﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Web;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Windows.Markup;
using Zeta;
using Zeta.Common;
using Zeta.Common.Plugins;
using Zeta.CommonBot;
using Zeta.CommonBot.Profile;
using Zeta.CommonBot.Profile.Composites;
using Zeta.Internals;
using Zeta.Internals.Actors;
using Zeta.Internals.Actors.Gizmos;
using Zeta.Internals.SNO;
using Zeta.Navigation;
using Zeta.TreeSharp;
using System.Text;
using Zeta.XmlEngine;
using GilesTrinity.ItemRules;

namespace GilesTrinity
{
    public class GilesTrinity : IPlugin
    {
        public Version Version { get { return new Version(1, 6, 3, 4); } }
        public string Author { get { return "GilesSmith"; } }
        public string Description { get { return "GilesTrinity version " + Version + " (v0.45)"; } }
        public string Name { get { return "GilesTrinity"; } }
        public bool Equals(IPlugin other) { return (other.Name == Name) && (other.Version == Version); }
        public static string SmtpServer = "smtp.gmail.com";
        public static StringBuilder EmailMessage = new StringBuilder();

        // **********************************************************************************************
        // *****   A few special variables, mainly for Giles use, just at the top for easy access   *****
        // **********************************************************************************************

        // Set the following to true, to disable file-logging for performance increase
        // WARNING: IF YOU GET CRASHES, ISSUES, OR PROBLEMS AND HAVE LOG-FILES DISABLED...
        // NOBODY CAN HELP YOU. Re-enable logging, wait for the issue/crash/problem, then report it with a log.
        // DO NOT DISABLE LOGGING AND THEN POST BLANK LOGS EXPECTING HELP!
        private const bool bDisableFileLogging = false;

        // For Giles use only (the data gathered by this being enabled is not useful for anyone else!):
        private const bool bLogBalanceDataForGiles = false;

        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // *****  Various Dictionary lists for combat-stuff, like priority lists, ignore lists etc. *****
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************

        // ****************************************************************
        // *****    Avoidance-related dictionaries/defaults           *****
        // ****************************************************************

        // A list of all the SNO's to avoid - you could add 
        private static readonly HashSet<int> hashAvoidanceSNOList = new HashSet<int>
            {
                // Arcane        Arcane 2      Desecrator   Poison Tree    Molten Core   Molten Core 2   Molten Trail   Plague Cloud   Ice Balls     
                219702,          221225,       84608,       5482,6578,     4803, 4804,   224225, 247987, 95868,         108869,        402, 223675,             
                // Bees-Wasps    Plague-Hands  Azmo Pools   Azmo fireball  Azmo bodies   Belial 1       Belial 2      
                5212,            3865,         123124,      123842,        123839,       161822,        161833, 
                // Sha-Ball      Mol Ball      Mage Fire    Diablo Prison  Diablo Meteor Ice-trail
                4103,            160154,       432,         168031,        214845,       260377,
				// Zolt Bubble	 Zolt Twister  Ghom Gas 	Maghda Proj    
                185924,			 139741,	   93837,		166686,
            };

        // A list of SNO's that are projectiles (so constantly look for new locations while avoiding)
        private static readonly HashSet<int> hashAvoidanceSNOProjectiles = new HashSet<int>
            {
                // Bees-Wasps  Sha-Ball   Mol Ball   Azmo fireball	Zolt Twister	Maghda Projectile
                5212,          4103,      160154,    123842,		139741,			166686,
            };

        // The rough radius of each avoidance thing (from centre to edge!) in feet
        private static readonly Dictionary<int, double> dictAvoidanceRadiusDefaults = new Dictionary<int, double>
            {
                // Arcane        Arcane 2        Desecrator     Poison Tree           Molten Core            Molten Core 2              Molten Trail   Plague Cloud   Ice Balls     
                {219702, 12},    {221225, 12},   {84608, 10},   {5482, 14},{6578,14}, {4803, 19},{4804, 19}, {224225, 19}, {247987,19}, {95868, 6},    {108869, 14},  {402, 20}, {223675, 20},             
                // Bees-Wasps    Plague-Hands    Azmo Pools     Azmo fireball    Azmo bodies    Belial 1        Belial 2      
                {5212, 10},      {3865, 12},     {123124, 54},  {123842, 16},    {123839, 47},  {161822, 20},   {161833, 20}, 
                // Sha-Ball      Mol Ball        Mage Fire      Diablo Prison    Diablo Meteor  Ice trail
                {4103, 8},       {160154, 8},    {432, 10},     {168031, 15},    {214845, 28},  {260377, 6},
				// Zolt Bubble	 Zolt Twister  	 Ghom Gas 		Maghda Proj    
                {185924, 20},	 {139741, 12},	 {93837, 18},	{166686, 8},
            };
        private static Dictionary<int, double> dictAvoidanceRadius = new Dictionary<int, double>(dictAvoidanceRadiusDefaults);

        // How much health to look for and avoid each AOE (1 = 100% health, 0.5 = 50% health etc.), different values for each class
        // ***************************
        // *****    Barbarians   *****
        // ***************************
        private static readonly Dictionary<int, double> dictAvoidanceHealthBarbDefaults = new Dictionary<int, double> 
        {
            // Arcane        Arcane 2        Desecrator      Poison Tree                  Molten Core          Molten Core 2             Molten Trail   Plague Cloud       Ice Balls     
            {219702, 1},     {221225, 1},    {84608, 1},     {5482, 0.55},{6578, 0.55},   {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.5},  {108869, 0.25},   {402, 0.85},{223675, 0.85},             
            // Bees-Wasps    Plague-Hands    Azmo Pools      Azmo fireball    Azmo bodies     Belial 1        Belial 2      
            {5212, 0.7},     {3865, 0.80},   {123124, 0.8},  {123842, 0.7},   {123839, 0.7},  {161822, 1},    {161833, 1}, 
            // Sha-Ball      Mol Ball        Mage Fire       Diablo Prison    Diablo Meteor   Ice trail
            {4103, 0},       {160154, 0},    {432, 0.2},     {168031, 1},     {214845, 0.5},  {260377, 0.5},
			// Zolt Bubble	 Zolt Twister	 Ghom Gas 		 Maghda Proj    
            {185924, 1},	 {139741, 0.7}, {93837, 0.5},	 {166686, 0.7},
        };
        private static Dictionary<int, double> dictAvoidanceHealthBarb = new Dictionary<int, double>(dictAvoidanceHealthBarbDefaults);
        // ***************************
        // *****      Monks      *****
        // ***************************
        private static readonly Dictionary<int, double> dictAvoidanceHealthMonkDefaults = new Dictionary<int, double>
        {
            // Arcane        Arcane 2       Desecrator     Poison Tree                   Molten Core          Molten Core 2            Molten Trail   Plague Cloud       Ice Balls     
            {219702, 1},     {221225, 1},   {84608, 1},    {5482, 0.65},{6578, 0.65},    {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.65}, {108869, 0.35},    {402, 0.85},{223675, 0.85},          
            // Bees-Wasps    Plague-Hands    Azmo Pools      Azmo fireball    Azmo bodies     Belial 1        Belial 2      
            {5212, 0.75},    {3865, 0.85},   {123124, 0.85}, {123842, 0.75},  {123839, 0.75}, {161822, 1},    {161833, 1}, 
            // Sha-Ball      Mol Ball        Mage Fire       Diablo Prison    Diablo Meteor   Ice trail
            {4103, 0},       {160154, 0},    {432, 0.3},     {168031, 1},     {214845, 0.5},  {260377, 0.5},
			// Zolt Bubble	 Zolt Twister	 Ghom Gas 		 Maghda Proj    
            {185924, 1},	 {139741, 0.7}, {93837, 0.5},	 {166686, 0.7},
        };
        private static Dictionary<int, double> dictAvoidanceHealthMonk = new Dictionary<int, double>(dictAvoidanceHealthMonkDefaults);
        // ***************************
        // *****     Wizards     *****
        // ***************************
        private static readonly Dictionary<int, double> dictAvoidanceHealthWizardDefaults = new Dictionary<int, double>
        {
            // Arcane        Arcane 2        Desecrator      Poison Tree                  Molten Core          Molten Core 2            Molten Trail   Plague Cloud       Ice Balls     
            {219702, 1},     {221225, 1},    {84608, 1},     {5482, 0.9},{6578, 0.9},     {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.9},  {108869, 0.9},     {402, 1},{223675, 1},          
            // Bees-Wasps    Plague-Hands    Azmo Pools      Azmo fireball    Azmo bodies     Belial 1        Belial 2      
            {5212, 1},       {3865, 1},      {123124, 1},    {123842, 1},     {123839, 1},    {161822, 1},    {161833, 1}, 
            // Sha-Ball      Mol Ball        Mage Fire       Diablo Prison    Diablo Meteor   Ice trail
            {4103, 0.85},    {160154, 0.10}, {432, 0.10},    {168031, 1},     {214845, 0.8},  {260377, 0.9},
			// Zolt Bubble	 Zolt Twister	 Ghom Gas 		 Maghda Proj    
            {185924, 1},	 {139741, 0.7}, {93837, 1},	 {166686, 0.7},
        };
        private static Dictionary<int, double> dictAvoidanceHealthWizard = new Dictionary<int, double>(dictAvoidanceHealthWizardDefaults);
        // ***************************
        // *****  Witch Doctors  *****
        // ***************************
        private static readonly Dictionary<int, double> dictAvoidanceHealthWitchDefaults = new Dictionary<int, double>
        {
            // Arcane        Arcane 2        Desecrator      Poison Tree                  Molten Core          Molten Core 2            Molten Trail   Plague Cloud       Ice Balls     
            {219702, 1},     {221225, 1},    {84608, 1},     {5482, 0.9},{6578, 0.9},     {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.9},  {108869, 0.9},     {402, 1},{223675, 1},           
            // Bees-Wasps    Plague-Hands    Azmo Pools      Azmo fireball    Azmo bodies     Belial 1        Belial 2      
            {5212, 1},       {3865, 1},      {123124, 1},    {123842, 1},     {123839, 1},    {161822, 1},    {161833, 1}, 
            // Sha-Ball      Mol Ball        Mage Fire       Diablo Prison    Diablo Meteor   Ice trail
            {4103, 0.85},    {160154, 0.10}, {432, 0.10},    {168031, 1},     {214845, 0.8},  {260377, 0.9},
			// Zolt Bubble	 Zolt Twister	 Ghom Gas 		 Maghda Proj    
            {185924, 1},	 {139741, 0.7}, {93837, 1},	 {166686, 0.7},
        };
        private static Dictionary<int, double> dictAvoidanceHealthWitch = new Dictionary<int, double>(dictAvoidanceHealthWitchDefaults);
        // ***************************
        // *****   Demon Hunter  *****
        // ***************************
        private static readonly Dictionary<int, double> dictAvoidanceHealthDemonDefaults = new Dictionary<int, double>
        {
            // Arcane        Arcane 2        Desecrator      Poison Tree                  Molten Core          Molten Core 2            Molten Trail   Plague Cloud       Ice Balls     
            {219702, 1},     {221225, 1},    {84608, 1},     {5482, 0.9},{6578, 0.9},     {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.9},  {108869, 0.9},     {402, 1},{223675, 1},             
            // Bees-Wasps    Plague-Hands    Azmo Pools      Azmo fireball    Azmo bodies     Belial 1        Belial 2      
            {5212, 1},       {3865, 1},      {123124, 1},    {123842, 1},     {123839, 1},    {161822, 1},    {161833, 1}, 
            // Sha-Ball      Mol Ball        Mage Fire       Diablo Prison    Diablo Meteor   Ice trail
            {4103, 0.85},   {160154, 0.20}, {432, 0.20},     {168031, 1},     {214845, 0.8},  {260377, 0.9},
			// Zolt Bubble	 Zolt Twister	 Ghom Gas 		 Maghda Proj    
            {185924, 1},	 {139741, 0.7}, {93837, 1},	 {166686, 0.7},
        };
        private static Dictionary<int, double> dictAvoidanceHealthDemon = new Dictionary<int, double>(dictAvoidanceHealthDemonDefaults);
        // The below thing should be UNCHANGED - it is automatically filled with the above dictionary sets based on your class - it defaults to barb but DOES CHANGE ITSELF!
        private static Dictionary<int, double> dictAvoidanceHealth = new Dictionary<int, double>(dictAvoidanceHealthBarb);

        // ****************************************************************
        // *****      Combat-related dictionaries/defaults            *****
        // ****************************************************************

        // A special list of things *NOT* to use whirlwind on (eg because they move too quick/WW is ineffective on)
        // 4304 = shielder fat dudes in act 3 hell zones
        private static readonly HashSet<int> hashActorSNOWhirlwindIgnore = new HashSet<int> { 
            4304, 5984, 5985, 5987, 5988,
         };

        // Very fast moving mobs (eg wasps), for special skill-selection decisions
        // 5212 = act 2 wasps
        private static readonly HashSet<int> hashActorSNOFastMobs = new HashSet<int> { 
            5212
         };

        // A list of crappy "summoned mobs" we should always ignore unless they are very close to us, eg "grunts", summoned skeletons etc.
        private static readonly HashSet<int> hashActorSNOShortRangeOnly = new HashSet<int> { 
            4084, 4085, 5395, 144315,
         };

        // Dictionary for priorities, like the skeleton summoner cos it keeps bringing new stuff
        private static readonly Dictionary<int, int> dictActorSNOPriority = new Dictionary<int, int> { 
            // Wood wraiths all this line (495 & 496 & 6572 & 139454 & 139456 & 170324 & 170325)
            {495, 901}, {496, 901}, {6572, 901}, {139454, 901}, {139456, 901}, {170324, 901}, {170325, 901},
            //intell -- added 4099 (act 2 fallen shaman)
			// Fallen Shaman prophets goblin Summoners (365 & 4100)
            {365, 1901}, {4099, 1901}, {4100, 1901},
            // The annoying grunts summoned by the above
            {4084, -401},
            // Wretched mothers that summon zombies in act 1 (6639)
            {6639, 951}, 
            // Fallen lunatic (4095)
            {4095, 2999},
            // Pestilence hands (4738)
            {4738, 1901}, 
             // Maghda and her minions
            {6031, 801}, {178512, 901},
            // Cydaea boss (95250)
            {95250, 1501},
            //intell
			//Cydaea Spiderlings (137139)
            {137139, -301},
            // GoatMutantshaman Elite (4304)
            {4304, 999},
            // GoatMutantshaman (4300)
            {4300, 901},
            // Succubus (5508)
            {5508, 801},
            // skeleton summoners (5387, 5388, 5389)
            {5387, 951}, {5388, 951}, {5389, 951}, 
            // Weak skeletons summoned by the above
            {5395, -401},
            // Wasp/Bees - Act 2 annoying flyers (5212)
            {5212, 751}, 
            // Dark summoner - summons the helion dogs (6035)
            {6035, 501}, 
            // Dark berserkers - has the huge damaging slow hit (6052)
            {6052, 501}, 
            // The giant undead fat grotesques that explode in act 1 (3847)
            {3847, 401}, 
            // Hive pods that summon flyers in act 1 (4152, 4153, 4154)
            {4152, 901}, {4153, 901}, {4154, 901}, 
            // Totems in act 1 that summon the ranged goatmen (166452)
            {166452, 901}, 
            // Totems in act 1 dungeons that summon skeletons (176907)
            {176907, 901},
			// Uber Bosses - Skeleton King {255929}, Maghda {256189} & Pets {219702} which must be killed first
			{255929, 2999}, {219702, 1999}, {256189, 999},
			// Uber Bosses - Zoltun Kulle {256508} & Siegebreaker {256187}
			// Siegebreaker removed so the focus remains on Zoltun Kulle until he is dead
			{256508, 2999},
			//{256508, 2999}, {256187, 1899}, 
			// Uber Bosses - Ghom {256709} & Rakanot {256711}
			{256709, 2999}, {256711, 1899},
         };
        // A flag to say whether any NONE-hashActorSNOWhirlwindIgnore things are around
        private static bool bAnyNonWWIgnoreMobsInRange = false;

        // A list of all known SNO's of treasure goblins/bandits etc.
        private static readonly HashSet<int> hashActorSNOGoblins = new HashSet<int> { 
            5984, 5985, 5987, 5988
         };
        // A list of ranged mobs that should be attacked even if they are outside of the routines current kill radius
        //4286 = act 1 goatman ranged; 5388 = act 1 skeleton summoner; 365, 4100, 4099 = fallen shamans; 4300, 4304 = goat shaman; 4738 = pestilence;
        //4299 = goat ranged; 62736, 130794 = demon flyer; 5508 = succubus
        private static readonly HashSet<int> hashActorSNORanged = new HashSet<int> { 
            365, 4099, 4100, 4304, 4300, 4738, 4299, 62736, 130794, 5508, 5388, 4286, 
         };
        // A list of bosses in the game, just to make CERTAIN they are treated as elites
        private static readonly HashSet<int> hashBossSNO = new HashSet<int> { 
            // Siegebreaker (96192), Azmodan (89690), Cydea (95250), Heart-thing (193077), Kulle (80509), Small Belial (220160), Big Belial (3349), Diablo 1 (114917), terror Diablo (133562)
            96192, 89690, 95250, 193077, 80509, 220160, 3349, 114917, 133562, 255929, 256711, 256508, 256187, 256189, 256709,
            // Diablo shadow clones (needs all of them, there is a male & female version of each class!)
            144001, 144003, 143996, 143994, 
            // Jondar (act 1 dungeons)
            86624, 
         };
        // IGNORE LIST / BLACKLIST - for units / monsters / npcs
        // Special blacklist for things like ravens, templar/scoundrel/enchantress in town, witch-doctor summons, tornado-animations etc. etc. that should never be attacked
        // Note: This is a MONSTER blacklist - so only stuff that needs to be ignored by the combat-engine. An "object" blacklist is further down!
        //intell -- added oldNecromancer (4798) and his Skeleton_A_Necromancer (183892)
        private static readonly HashSet<int> hashActorSNOIgnoreBlacklist = new HashSet<int> { 
            5840, 111456, 5013, 5014, 205756, 205746, 4182, 4183, 4644, 4062, 4538, 52693, 162575, 2928, 51291, 51292, 
            96132, 90958, 90959, 80980, 51292, 51291, 2928, 3546, 129345, 81857, 138428, 81857, 60583, 170038, 174854, 190390, 
            194263, 174900, 87189, 90072, 107031, 106584, 186130, 187265, 201426, 201242, 200969, 201423, 
            201438, 201464, 201454, 108012, 103279, 89578, 74004, 84531, 84538, 89579, 190492, 209133, 6318, 107705, 105681, 89934, 
            89933, 182276, 117574, 182271, 182283, 182278, 128895, 81980, 82111, 81226, 81227, 107067, 106749,
            107107, 107112, 106731, 107752, 107829, 90321, 107828, 121327, 185391, 249320, 81232, 81231, 81239, 81515, 210433, 195414,
            80758, 80757, 80745, 81229, 81230, 82109, 83024, 83025, 82972, 83959, 249190, 251396, 138472, 118260, 200226, 192654, 245828, 
            215103, 132951, 217508, 199998, 199997, 114527, 245910, 169123, 123885, 169890, 168878, 169891, 169077, 169904, 169907, 
            169906, 169908, 169905, 169909, 179780, 179778, 179772, 179779, 179776, 122305, 110959, 103235, 103215, 105763, 103217, 51353, 80140, 
            4176, 178664, 173827, 133741, 159144, 181748, 159098, 206569, 200706, 5895, 5896, 5897, 5899, 4686, 87037, 85843, 103919, 249338, 
            251416, 249192, 80812, 4798, 183892,196899, 196900, 196903, 223333, 220636, 218951, 245838, 
			//blackhawk
            3384,
            //bone pile
         };

        // Three special lists used purely for checking for the existance of a player's summoned mystic ally, gargantuan, or zombie dog
        private static readonly HashSet<int> hashMysticAlly = new HashSet<int> { 169123, 123885, 169890, 168878, 169891, 169077, 169904, 169907, 169906, 169908, 169905, 169909 };
        private static readonly HashSet<int> hashGargantuan = new HashSet<int> { 179780, 179778, 179772, 179779, 179776, 122305 };
        private static readonly HashSet<int> hashZombie = new HashSet<int> { 110959, 103235, 103215, 105763, 103217, 51353 };
        private static readonly HashSet<int> hashDHPets = new HashSet<int> { 178664, 173827, 133741, 159144, 181748, 159098 };


        // **********************************************************************************************
        // *****         World-object dictionaries eg large object lists, ignore lists etc.         *****
        // **********************************************************************************************

        // A list of SNO's to *FORCE* to type: Item. (BE CAREFUL WITH THIS!).
        // 166943 = infernal key
        private static readonly HashSet<int> hashForceSNOToItemList = new HashSet<int> { 
            166943, 
         };
        // Interactable whitelist - things that need interacting with like special wheels, levers - they will be blacklisted for 30 seconds after one-use
        private static readonly HashSet<int> hashSNOInteractWhitelist = new HashSet<int> { 
            54908, 56686, 54850, 454, 211999, 52685, 54882,  
         };
        // NOTE: you don't NEED interactable SNO's listed here. But if they are listed here, *THIS* is the range at which your character will try to walk to within the object 
        // BEFORE trying to actually "click it". Certain objects need you to get very close, so it's worth having them listed with low interact ranges
        private static readonly Dictionary<int, int> dictInteractableRange = new Dictionary<int, int> { 
            {56686, 4}, {52685, 4}, {54850, 14},  {54882, 40}, {54908, 4}
         };
        // 174900 = fire-spewers (demonic forge) in Act 3, 54908 = iron gates
        // Navigation obstacles for standard navigation down dungeons etc. to help DB movement
        // MAKE SURE you add the *SAME* SNO to the "size" dictionary below, and include a reasonable size (keep it smaller rather than larger) for the SNO.
        private static readonly HashSet<int> hashSNONavigationObstacles = new HashSet<int> {
            174900, 191459, 54908
        };
        // Size of the navigation obstacles above (actual SNO list must be matching the above list!)
        public static readonly Dictionary<int, int> dictSNONavigationSize = new Dictionary<int, int> { 
            {174900, 10}, {191459, 13}, {54908, 10}
         };
        // Destructible things that are very large and need breaking at a bigger distance - eg logstacks, large crates, carts, etc.
        private static readonly Dictionary<int, int> dictSNOExtendedDestructRange = new Dictionary<int, int> { 
            {2972, 10}, {80357, 16}, {116508, 10}, {113932, 8}, {197514, 18}, {108587, 8}, {108618, 8}, {108612, 8}, {116409, 18}, {121586, 18},
            {195101, 10}, {195108, 25}, {170657, 8}, {181228, 10}, {211959, 25}, {210418, 25}, {174496, 8}, {193963, 10}, {159066, 12}, {160570, 12},
            {55325, 14}, {5718, 14}, {5909, 10}, {5792, 8}, {108194, 8}, {129031, 20}, {192867, 8}, {155255, 8}
         };
        // Destructible things that need targeting by a location instead of an ACDGUID (stuff you can't "click on" to destroy in-game)
        private static readonly HashSet<int> hashDestructableLocationTarget = new HashSet<int> { 
            170657, 116409, 121586, 
         };
        // Resplendent chest SNO list
        private static readonly HashSet<int> hashSNOContainerResplendant = new HashSet<int> { 
            62873, 95011, 81424, 108230, 111808, 111809, 211861,
         };
        // Chests/average-level containers that deserve a bit of extra radius (ie - they are more worthwhile to loot than "random" misc/junk containers)
        private static readonly HashSet<int> hashSNOContainerWhitelist = new HashSet<int> { 
            62859, 62865, 62872, 78790, 79016, 94708, 96522, 130170, 108122, 111870, 111947, 213447, 213446, 51300, 179865, 109264, 212491, 210422, 
         };
        // IGNORE LIST / BLACKLIST - for world objects
        // World objects that should always be ignored - eg certain destructables, certain containers, etc. - anything handled as a "world object" rather than a monster
        private static readonly HashSet<int> hashSNOIgnoreBlacklist = new HashSet<int> { 
            163449, 78030, 2909, 58283, 58299, 58309, 58321, 87809, 88005, 90150, 91600, 97023, 97350, 97381, 72689, 121327, 54952, 54515, 3340, 122076, 123640, 
            60665, 60844, 78554, 86400, 86428, 81699, 86266, 86400, 110769, 192466, 211456, 6190, 80002, 104596, 58836, 104827, 74909, 6155, 6156, 6158, 6159, 75132,
            181504, 91688, 3016, 3007, 3011, 3014, 130858, 131573, 214396, 182730, 226087, 141639, 206569, 15119, 54413, 54926, 2979, 56416, 53802, 5776, 3949, 
            108490, 52833, 
			//bone pile
            //192867,

         };


        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // *****                  Majority of variables and class/structs used etc.                 *****
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************

        // **********************************************************************************************
        // *****     GilesItemType,  GilesBaseItemType and GilesObjectType - for readability etc.   *****
        // **********************************************************************************************

        // Primary "lowest level" item type (eg EXACTLY what kind of item it is)
        public enum GilesItemType
        {
            Unknown,
            Axe,
            CeremonialKnife,
            HandCrossbow,
            Dagger,
            FistWeapon,
            Mace,
            MightyWeapon,
            Spear,
            Sword,
            Wand,
            TwoHandAxe,
            TwoHandBow,
            TwoHandDaibo,
            TwoHandCrossbow,
            TwoHandMace,
            TwoHandMighty,
            TwoHandPolearm,
            TwoHandStaff,
            TwoHandSword,
            StaffOfHerding,
            Mojo,
            Source,
            Quiver,
            Shield,
            Amulet,
            Ring,
            Belt,
            Boots,
            Bracers,
            Chest,
            Cloak,
            Gloves,
            Helm,
            Pants,
            MightyBelt,
            Shoulders,
            SpiritStone,
            VoodooMask,
            WizardHat,
            FollowerEnchantress,
            FollowerScoundrel,
            FollowerTemplar,
            CraftingMaterial,
            CraftTome,
            Ruby,
            Emerald,
            Topaz,
            Amethyst,
            SpecialItem,
            CraftingPlan,
            HealthPotion,
            Dye,
            HealthGlobe,
            InfernalKey,
        }
        // Base types, eg "one handed weapons" "armors" etc.
        public enum GilesBaseItemType
        {
            Unknown,
            WeaponOneHand,
            WeaponTwoHand,
            WeaponRange,
            Offhand,
            Armor,
            Jewelry,
            FollowerItem,
            Misc,
            Gem,
            HealthGlobe
        }
        // Generic object types - eg a monster, an item to pickup, a shrine to click etc.
        public enum GilesObjectType
        {
            Unit,
            Shrine,
            Interactable,
            Destructible,
            Barricade,
            Container,
            Item,
            Gold,
            Globe,
            Avoidance,
            Backtrack,
            Unknown
        }

        // **********************************************************************************************
        // *****                          Current cached player data                                *****
        // **********************************************************************************************

        // Just stores the data on YOU, well, your character's current status - for readability of those variables more than anything, but also caching

        public class GilesPlayerCache
        {
            public DateTime lastUpdatedPlayer { get; set; }
            public bool bIsIncapacitated { get; set; }
            public bool bIsRooted { get; set; }
            public bool bIsInTown { get; set; }
            public double dCurrentHealthPct { get; set; }
            public double dCurrentEnergy { get; set; }
            public double dCurrentEnergyPct { get; set; }
            public double dDiscipline { get; set; }
            public double dDisciplinePct { get; set; }
            public Vector3 vCurrentPosition { get; set; }
            public bool bWaitingForReserveEnergy { get; set; }
            public int iMyDynamicID { get; set; }
            public int iMyLevel { get; set; }

            public GilesPlayerCache(DateTime lastupdated, bool incapacitated, bool isrooted, bool isintown, double currenthealth, double currentenergy, double currentenergypct,
                double discipline, double disciplinepct, Vector3 currentpos, bool waitingreserve, int dynamicid, int mylevel)
            {
                lastUpdatedPlayer = lastupdated;
                bIsIncapacitated = incapacitated;
                bIsRooted = isrooted;
                bIsInTown = isintown;
                dCurrentHealthPct = currenthealth;
                dCurrentEnergy = currentenergy;
                dCurrentEnergyPct = currentenergypct;
                dDiscipline = discipline;
                dDisciplinePct = disciplinepct;
                vCurrentPosition = currentpos;
                bWaitingForReserveEnergy = waitingreserve;
                iMyDynamicID = dynamicid;
                iMyLevel = mylevel;
            }
        }

        // **********************************************************************************************
        // *****                      GilesObject type used to cache all data                       *****
        // **********************************************************************************************

        // Let's me create an object list with ONLY the data I need read from D3 memory, and then read from this while
        // Handling movement and interaction with the target - whether the target is a shrine, an item or a monster
        // Completely minimizing the D3 memory reads to the bare minimum

        public class GilesObject
        {
            // Generic stuff applicable to all objects
            public GilesObjectType ThisGilesObjectType { get; set; }
            public double dThisWeight { get; set; }
            public Vector3 vThisPosition { get; set; }
            public float fCentreDistance { get; set; }
            public float fRadiusDistance { get; set; }
            public string sThisInternalName { get; set; }
            public int iThisACDGUID { get; set; }
            public int iThisRActorGuid { get; set; }
            public int iThisDynamicID { get; set; }
            public int iThisBalanceID { get; set; }
            public int iThisActorSNO { get; set; }
            // Item/gold/other stuff
            public int iThisLevel { get; set; }
            public int iThisGoldAmount { get; set; }
            public bool bThisOneHanded { get; set; }
            public ItemQuality ThisQuality { get; set; }
            public ItemType ThisDBItemType { get; set; }
            public FollowerType ThisFollowerType { get; set; }
            public GilesItemType ThisGilesItemType { get; set; }
            // Monster/unit stuff
            public bool bThisElite { get; set; }
            public bool bThisRare { get; set; }
            public bool bThisUnique { get; set; }
            public bool bThisMinion { get; set; }
            public bool bThisTreasureGoblin { get; set; }
            public bool bThisEliteRareUnique { get; set; }
            public bool bThisBoss { get; set; }
            public bool bThisAttackable { get; set; }
            public double iThisHitPoints { get; set; }
            public float fThisRadius { get; set; }
            public bool bForceLeapAgainst { get; set; }
            public MonsterSize unit_thisMonsterSize { get; set; }
            // A reference to the original object for fast updates
            public DiaUnit unit_thisDiaUnit { get; set; }

            public GilesObject(Vector3 thisposition, GilesObjectType thisobjecttype = GilesObjectType.Unknown, double thisweight = 0, float thisdistance = 0f, float thisradiusdistance = 0f,
                string thisinternalname = "", int thisacdguid = -1, int thisractorguid = -1, int thisdynamicid = -1, int thisbalanceid = -1, int thisactorsno = -1, int thislevel = -1, int thisgold = -1,
                bool thisonehand = true, ItemQuality thisquality = ItemQuality.Invalid, ItemType thisitemtype = ItemType.Unknown, FollowerType thisfollowertype = FollowerType.None,
                GilesItemType thisgilestype = GilesItemType.Unknown, bool thiselite = false, bool thisrare = false, bool thisunique = false, bool thisminion = false,
                bool thistreasure = false, bool thisboss = false, bool thisattackable = false, double thishitpoints = 0d, float thisradius = 0f, MonsterSize thismonstersize = MonsterSize.Unknown,
                DiaUnit thisunit = null, bool uniquerareelite = false, bool forceleap = false)
            {
                vThisPosition = thisposition;
                ThisGilesObjectType = thisobjecttype;
                dThisWeight = thisweight;
                fCentreDistance = thisdistance;
                fRadiusDistance = thisradiusdistance;
                sThisInternalName = thisinternalname;
                iThisACDGUID = thisacdguid;
                iThisRActorGuid = thisractorguid;
                iThisDynamicID = thisdynamicid;
                iThisBalanceID = thisbalanceid;
                iThisActorSNO = thisactorsno;

                iThisLevel = thislevel;
                iThisGoldAmount = thisgold;
                bThisOneHanded = thisonehand;
                ThisQuality = thisquality;
                ThisDBItemType = thisitemtype;
                ThisFollowerType = thisfollowertype;
                ThisGilesItemType = thisgilestype;

                bThisElite = thiselite;
                bThisRare = thisrare;
                bThisUnique = thisunique;
                bThisMinion = thisminion;
                bThisTreasureGoblin = thistreasure;
                bThisEliteRareUnique = uniquerareelite;
                bThisBoss = thisboss;
                bThisAttackable = thisattackable;
                iThisHitPoints = thishitpoints;
                fThisRadius = thisradius;
                unit_thisMonsterSize = thismonstersize;

                unit_thisDiaUnit = thisunit;

                bForceLeapAgainst = forceleap;
            }

            // For cloning the object (and not simply referencing it)
            public GilesObject Clone()
            {
                GilesObject newGilesObject = new GilesObject(vThisPosition, ThisGilesObjectType, dThisWeight, fCentreDistance, fRadiusDistance, sThisInternalName, iThisACDGUID,
                    iThisRActorGuid, iThisDynamicID, iThisBalanceID, iThisActorSNO, iThisLevel, iThisGoldAmount, bThisOneHanded,
                    ThisQuality, ThisDBItemType, ThisFollowerType, ThisGilesItemType, bThisElite, bThisRare, bThisUnique,
                    bThisMinion, bThisTreasureGoblin, bThisBoss, bThisAttackable, iThisHitPoints, fThisRadius, unit_thisMonsterSize, unit_thisDiaUnit, bThisEliteRareUnique, bForceLeapAgainst);
                return newGilesObject;
            }
        }


        // **********************************************************************************************
        // *****   GilesCachedACDItem - Special caching class to help with backpack-item handling   *****
        // **********************************************************************************************

        // So we can make an object, read all item stats from a backpack item *ONCE*, then store it here while my behavior trees process everything
        // Preventing any need for calling D3 memory again after the initial read (every D3 memory read is a chance for a DB crash/item mis-read/stuck!)

        public class GilesCachedACDItem
        {
            public string ThisInternalName { get; set; }
            public string ThisRealName { get; set; }
            public int ThisLevel { get; set; }
            public ItemQuality ThisQuality { get; set; }
            public int ThisGoldAmount { get; set; }
            public int ThisBalanceID { get; set; }
            public int ThisDynamicID { get; set; }
            public float ThisWeaponDPS { get; set; }
            public bool ThisOneHanded { get; set; }
            public DyeType ThisDyeType { get; set; }
            public ItemType ThisDBItemType { get; set; }
            public FollowerType ThisFollowerType { get; set; }
            public bool IsUnidentified { get; set; }
            public int ThisItemStackQuantity { get; set; }
            public float Dexterity { get; set; }
            public float Intelligence { get; set; }
            public float Strength { get; set; }
            public float Vitality { get; set; }
            public float LifePercent { get; set; }
            public float LifeOnHit { get; set; }
            public float LifeSteal { get; set; }
            public float HealthPerSecond { get; set; }
            public float MagicFind { get; set; }
            public float GoldFind { get; set; }
            public float MovementSpeed { get; set; }
            public float PickUpRadius { get; set; }
            public float Sockets { get; set; }
            public float CritPercent { get; set; }
            public float CritDamagePercent { get; set; }
            public float AttackSpeedPercent { get; set; }
            public float MinDamage { get; set; }
            public float MaxDamage { get; set; }
            public float BlockChance { get; set; }
            public float Thorns { get; set; }
            public float ResistAll { get; set; }
            public float ResistArcane { get; set; }
            public float ResistCold { get; set; }
            public float ResistFire { get; set; }
            public float ResistHoly { get; set; }
            public float ResistLightning { get; set; }
            public float ResistPhysical { get; set; }
            public float ResistPoison { get; set; }
            public float WeaponDamagePerSecond { get; set; }
            public float ArmorBonus { get; set; }
            public float MaxDiscipline { get; set; }
            public float MaxMana { get; set; }
            public float ArcaneOnCrit { get; set; }
            public float ManaRegen { get; set; }
            public float GlobeBonus { get; set; }

            public ACDItem ACDItem { get; set; }

            public GilesCachedACDItem(string internalname, string realname, int level, ItemQuality quality, int goldamount, int balanceid, int dynamicid, float dps,
                bool onehanded, DyeType dyetype, ItemType dbitemtype, FollowerType dbfollowertype, bool unidentified, int stackquantity, ItemStats thesestats, ACDItem item)
            {
                ThisInternalName = internalname;
                ThisRealName = realname;
                ThisLevel = level;
                ThisQuality = quality;
                ThisGoldAmount = goldamount;
                ThisBalanceID = balanceid;
                ThisDynamicID = dynamicid;
                ThisWeaponDPS = dps;
                ThisOneHanded = onehanded;
                ThisDyeType = dyetype;
                ThisDBItemType = dbitemtype;
                ThisFollowerType = dbfollowertype;
                IsUnidentified = unidentified;
                ThisItemStackQuantity = stackquantity;
                Dexterity = thesestats.Dexterity;
                Intelligence = thesestats.Intelligence;
                Strength = thesestats.Strength;
                Vitality = thesestats.Vitality;
                LifePercent = thesestats.LifePercent;
                LifeOnHit = thesestats.LifeOnHit;
                LifeSteal = thesestats.LifeSteal;
                HealthPerSecond = thesestats.HealthPerSecond;
                MagicFind = thesestats.MagicFind;
                GoldFind = thesestats.GoldFind;
                MovementSpeed = thesestats.MovementSpeed;
                PickUpRadius = thesestats.PickUpRadius;
                Sockets = thesestats.Sockets;
                CritPercent = thesestats.CritPercent;
                CritDamagePercent = thesestats.CritDamagePercent;
                AttackSpeedPercent = thesestats.AttackSpeedPercent;
                MinDamage = thesestats.MinDamage;
                MaxDamage = thesestats.MaxDamage;
                BlockChance = thesestats.BlockChance;
                Thorns = thesestats.Thorns;
                ResistAll = thesestats.ResistAll;
                ResistArcane = thesestats.ResistArcane;
                ResistCold = thesestats.ResistCold;
                ResistFire = thesestats.ResistFire;
                ResistHoly = thesestats.ResistHoly;
                ResistLightning = thesestats.ResistLightning;
                ResistPhysical = thesestats.ResistPhysical;
                ResistPoison = thesestats.ResistPoison;
                WeaponDamagePerSecond = thesestats.WeaponDamagePerSecond;
                ArmorBonus = thesestats.ArmorBonus;
                MaxDiscipline = thesestats.MaxDiscipline;
                MaxMana = thesestats.MaxMana;
                ArcaneOnCrit = thesestats.ArcaneOnCrit;
                ManaRegen = thesestats.ManaRegen;
                GlobeBonus = thesestats.HealthGlobeBonus;
                ACDItem = item;
            }
        }

        // **********************************************************************************************
        // *****     Giles Power - used when picking a power to use to cache where/how to use it    *****
        // **********************************************************************************************

        public class GilesPower
        {
            public SNOPower powerThis { get; set; }
            public float iMinimumRange { get; set; }
            public Vector3 vTargetLocation { get; set; }
            public int iTargetWorldID { get; set; }
            public int iTargetGUID { get; set; }
            public int iForceWaitLoopsBefore { get; set; }
            public int iForceWaitLoopsAfter { get; set; }
            public bool bWaitWhileAnimating { get; set; }

            public GilesPower(SNOPower thispower, float thisrange, Vector3 thisloc, int thisworld, int thisguid, int waitloops, int afterloops, bool repeat)
            {
                powerThis = thispower;
                iMinimumRange = thisrange;
                vTargetLocation = thisloc;
                iTargetWorldID = thisworld;
                iTargetGUID = thisguid;
                iForceWaitLoopsBefore = waitloops;
                iForceWaitLoopsAfter = afterloops;
                bWaitWhileAnimating = repeat;
            }
        }

        // **********************************************************************************************
        // *****                    Obstacles for quick mapping of paths etc.                       *****
        // **********************************************************************************************

        public class GilesObstacle
        {
            public Vector3 vThisLocation { get; set; }
            public float fThisRadius { get; set; }
            public int iThisSNOID { get; set; }
            public double dThisWeight { get; set; }

            public GilesObstacle(Vector3 thislocation, float thisradius, int thissnoid, double thisweight = 0)
            {
                vThisLocation = thislocation;
                fThisRadius = thisradius;
                iThisSNOID = thissnoid;
                dThisWeight = thisweight;
            }
        }

        // **********************************************************************************************
        // *****                        Super Special Giles Sauce Data Caching                      *****
        // **********************************************************************************************

        public class GilesGameBalanceDataCache
        {
            public int iThisItemLevel { get; set; }
            public ItemType thisItemType { get; set; }
            public bool bThisOneHand { get; set; }
            public FollowerType thisFollowerType { get; set; }

            public GilesGameBalanceDataCache(int itemlevel, ItemType itemtype, bool onehand, FollowerType followertype)
            {
                iThisItemLevel = itemlevel;
                thisItemType = itemtype;
                bThisOneHand = onehand;
                thisFollowerType = followertype;
            }
        }

        // **********************************************************************************************
        // *****    Item Stats Class and Variables - for the detailed item drop/pickup etc. stats   *****
        // **********************************************************************************************

        private class GilesItemStats
        {
            public double iTotal { get; set; }
            public double[] iTotalPerQuality { get; set; }
            public double[] iTotalPerLevel { get; set; }
            public double[,] iTotalPerQPerL { get; set; }
            public double iTotalPotions { get; set; }
            public double[] iPotionsPerLevel { get; set; }
            public double iTotalGems { get; set; }
            public double[] iGemsPerType { get; set; }
            public double[] iGemsPerLevel { get; set; }
            public double[,] iGemsPerTPerL { get; set; }
            public double iTotalInfernalKeys { get; set; }

            public GilesItemStats(double total, double[] totalperq, double[] totalperl, double[,] totalperqperl, double totalpotions, double[] potionsperlevel, double totalgems,
                double[] gemspertype, double[] gemsperlevel, double[,] gemspertperl, double totalkeys)
            {
                iTotal = total;
                iTotalPerQuality = totalperq;
                iTotalPerLevel = totalperl;
                iTotalPerQPerL = totalperqperl;
                iTotalPotions = totalpotions;
                iPotionsPerLevel = potionsperlevel;
                iTotalGems = totalgems;
                iGemsPerType = gemspertype;
                iGemsPerLevel = gemsperlevel;
                iGemsPerTPerL = gemspertperl;
                iTotalInfernalKeys = totalkeys;
            }
        }

        // **********************************************************************************************
        // *****                            Current plugin settings                                 *****
        // **********************************************************************************************
        public class GilesSettings
        {
            public bool bUseGilesFilters { get; set; }
            public int iMinimumGoldStack { get; set; }
            public double iNeedPointsToKeepJewelry { get; set; }
            public double iNeedPointsToKeepArmor { get; set; }
            public double iNeedPointsToKeepWeapon { get; set; }
            public int iFilterPotions { get; set; }
            public int iFilterLegendary { get; set; }
            public int iFilterBlueWeapons { get; set; }
            public int iFilterYellowWeapons { get; set; }
            public int iFilterBlueArmor { get; set; }
            public int iFilterYellowArmor { get; set; }
            public int iFilterBlueJewelry { get; set; }
            public int iFilterYellowJewelry { get; set; }
            public int iFilterGems { get; set; }
            public int iFilterMisc { get; set; }
            public int iFilterPotionLevel { get; set; }
            public bool bGemsEmerald { get; set; }
            public bool bGemsAmethyst { get; set; }
            public bool bGemsTopaz { get; set; }
            public bool bGemsRuby { get; set; }
            public bool bSalvageJunk { get; set; }
            public bool bPickupCraftTomes { get; set; }
            public bool bPickupPlans { get; set; }
            public bool bPickupFollower { get; set; }
            // Combat replacer config settings
            public bool bEnableBacktracking { get; set; }
            public bool bEnableAvoidance { get; set; }
            public bool bEnableGlobes { get; set; }
            public bool bEnableCriticalMass { get; set; }
            public int iTreasureGoblinPriority { get; set; }
            public double iMonsterKillRange { get; set; }
            public bool bOutOfCombatMovementPowers { get; set; }
            public bool bExtendedKillRange { get; set; }
            public bool bSelectiveWhirlwind { get; set; }
            public bool bWaitForWrath { get; set; }
            public bool bGoblinWrath { get; set; }
            public bool bFuryDumpWrath { get; set; }
            public bool bFuryDumpAlways { get; set; }
            public int iKillLootDelay { get; set; }
            public int iDHVaultMovementDelay { get; set; }
            public bool bMonkInnaSet { get; set; }
            public bool bWaitForArchon { get; set; }
            public bool bKiteOnlyArchon { get; set; }
            public bool bWrath90Seconds { get; set; }
            // World object handler config settings
            public bool bIgnoreAllShrines { get; set; }
            public bool bIgnoreCorpses { get; set; }
            public double iContainerOpenRange { get; set; }
            public double iDestructibleAttackRange { get; set; }
            // Performance stuff
            public bool bEnableTPS { get; set; }
            public double iTPSAmount { get; set; }
            public bool bDebugInfo { get; set; }
            // Enable Prowl
            public bool bEnableProwl { get; set; }
            public bool bEnableAndroid { get; set; }
            public bool bEnableLegendaryNotifyScore { get; set; }
            public double iNeedPointsToNotifyJewelry { get; set; }
            public double iNeedPointsToNotifyArmor { get; set; }
            public double iNeedPointsToNotifyWeapon { get; set; }
            // Enable Email
            public bool bEnableEmail { get; set; }
            // Log stuck points
            public bool bLogStucks { get; set; }
            // Enable unstucker
            public bool bEnableUnstucker { get; set; }
            public bool bEnableProfileReloading { get; set; }
            // Pot & Globe usage
            public double dEmergencyHealthPotionBarb { get; set; }
            public double dEmergencyHealthGlobeBarb { get; set; }
            public double dEmergencyHealthPotionMonk { get; set; }
            public double dEmergencyHealthGlobeMonk { get; set; }
            public double dEmergencyHealthPotionWiz { get; set; }
            public double dEmergencyHealthGlobeWiz { get; set; }
            public double dEmergencyHealthPotionWitch { get; set; }
            public double dEmergencyHealthGlobeWitch { get; set; }
            public double dEmergencyHealthPotionDemon { get; set; }
            public double dEmergencyHealthGlobeDemon { get; set; }

            public int iKiteDistanceBarb { get; set; }
            public int iKiteDistanceWiz { get; set; }
            public int iKiteDistanceWitch { get; set; }
            public int iKiteDistanceDemon { get; set; }
            // Defaults For All Settings can be set here
            public GilesSettings(bool usegilesfilters = true, int mingoldstack = 300, double pointsj = 15000, double pointsa = 16000, double pointsw = 70000, int filterpot = 2,
                int filterbluew = 63, int filteryelloww = 61, int filterbluea = 0, int filteryellowa = 61, int filterbluej = 0, int filteryellowj = 54, int filtergems = 60,
                int filtermisc = 60, int filterpotlevel = 60, bool emerald = true, bool amethyst = false, bool topaz = false, bool ruby = true, bool salvage = false, bool pickupcrafttomes = false,
                bool pickupplans = true, bool pickupfollower = true, bool enablebacktracking = false, bool enableavoidance = true, bool enableglobes = true, bool enablecriticalmass = false,
                int treasuregoblins = 2, double monsterkillrange = 18, bool outofcombatmovement = true, bool ignoreshrines = false, bool ignorecorpses = true, double containeropen = 15,
                double destructibleattack = 6, bool enabletps = false, double tpsamount = 10, bool logstucks = false, bool enableunstucker = true, bool extendedrange = true, bool selectiveww = false,
                bool debuginfo = false, double healthpot0 = 0.42, double healthpot1 = 0.46, double healthpot2 = 0.7, double healthpot3 = 0.7, double healthpot4 = 0.7,
                double healthglobe0 = 0.55, double healthglobe1 = 0.6, double healthglobe2 = 0.8, double healthglobe3 = 0.8, double healthglobe4 = 0.8, bool enableprowl = false, bool enableandroid = false,
                double pointsnotifyj = 28000, double pointsnotifya = 30000, double pointsnotifyw = 100000, int killlootdelay = 800, int vaultdelay = 400,
                bool waitforwrath = true, bool goblinwrath = true, bool furydumpwrath = true, bool furydumpalways = false, int filterlegendary = 1, bool profilereload = false,
                bool monkinna = false, int kitebarb = 0, int kitewiz = 0, int kitewitch = 0, int kitedemon = 0, bool waitarchon = true, bool kitearchon = false, bool wrath90 = false, bool enableEmail = false,
                bool bEnableLegendaryNotifyScore = false)
            {
                bUseGilesFilters = usegilesfilters;
                iMinimumGoldStack = mingoldstack;
                iNeedPointsToKeepJewelry = pointsj;
                iNeedPointsToKeepArmor = pointsa;
                iNeedPointsToKeepWeapon = pointsw;
                iFilterPotions = filterpot;
                iFilterLegendary = filterlegendary;
                iFilterBlueWeapons = filterbluew;
                iFilterYellowWeapons = filteryelloww;
                iFilterBlueArmor = filterbluea;
                iFilterYellowArmor = filteryellowa;
                iFilterBlueJewelry = filterbluej;
                iFilterYellowJewelry = filteryellowj;
                iFilterGems = filtergems;
                iFilterMisc = filtermisc;
                iFilterPotionLevel = filterpotlevel;
                bGemsEmerald = emerald;
                bGemsAmethyst = amethyst;
                bGemsTopaz = topaz;
                bGemsRuby = ruby;
                bSalvageJunk = salvage;
                bPickupCraftTomes = pickupcrafttomes;
                bPickupPlans = pickupplans;
                bPickupFollower = pickupfollower;
                // Combat replacer config settings
                bEnableBacktracking = enablebacktracking;
                bEnableAvoidance = enableavoidance;
                bEnableGlobes = enableglobes;
                bEnableCriticalMass = enablecriticalmass;
                iTreasureGoblinPriority = treasuregoblins;
                iMonsterKillRange = monsterkillrange;
                bOutOfCombatMovementPowers = outofcombatmovement;
                bExtendedKillRange = extendedrange;
                bSelectiveWhirlwind = selectiveww;
                bWaitForWrath = waitforwrath;
                bGoblinWrath = goblinwrath;
                bFuryDumpWrath = furydumpwrath;
                bFuryDumpAlways = furydumpalways;
                iKillLootDelay = killlootdelay;
                iDHVaultMovementDelay = vaultdelay;
                bMonkInnaSet = monkinna;
                bWrath90Seconds = wrath90;
                // World object handler config settings
                bIgnoreAllShrines = ignoreshrines;
                bIgnoreCorpses = ignorecorpses;
                iContainerOpenRange = containeropen;
                iDestructibleAttackRange = destructibleattack;
                // Advanced stuff
                bEnableTPS = enabletps;
                iTPSAmount = tpsamount;
                bLogStucks = logstucks;
                bEnableUnstucker = enableunstucker;
                bEnableProfileReloading = profilereload;
                bDebugInfo = debuginfo;
                // Enable prowl
                bEnableProwl = enableprowl;
                bEnableAndroid = enableandroid;
                this.bEnableLegendaryNotifyScore = bEnableLegendaryNotifyScore;
                iNeedPointsToNotifyJewelry = pointsnotifyj;
                iNeedPointsToNotifyArmor = pointsnotifya;
                iNeedPointsToNotifyWeapon = pointsnotifyw;
                // Enable email
                this.bEnableEmail = enableEmail;
                // Globes and pots
                dEmergencyHealthPotionBarb = healthpot0;
                dEmergencyHealthPotionMonk = healthpot1;
                dEmergencyHealthPotionWiz = healthpot2;
                dEmergencyHealthPotionWitch = healthpot3;
                dEmergencyHealthPotionDemon = healthpot4;
                dEmergencyHealthGlobeBarb = healthglobe0;
                dEmergencyHealthGlobeMonk = healthglobe1;
                dEmergencyHealthGlobeWiz = healthglobe2;
                dEmergencyHealthGlobeWitch = healthglobe3;
                dEmergencyHealthGlobeDemon = healthglobe4;
                iKiteDistanceBarb = kitebarb;
                iKiteDistanceWiz = kitewiz;
                iKiteDistanceWitch = kitewitch;
                iKiteDistanceDemon = kitedemon;
                bWaitForArchon = waitarchon;
                bKiteOnlyArchon = kitearchon;
            }
        }
        public static GilesSettings settings = new GilesSettings();
        // *****************************************
        // *****    End of config variables    *****
        // *****************************************

        // **********************************************************************************************
        // *****                       End of classes, actual variables now                         *****
        // **********************************************************************************************
        
        // instantiating the interpreter for itemruleset
        private static Interpreter interpreter = new Interpreter();

        // I create so many variables that it's a pain in the arse to categorize them
        // So I just throw them all here for quick searching, reference etc.
        // I've tried to make most variable names be pretty damned obvious what they are for!
        // I've also commented a lot of variables/sections of variables to explain what they are for, incase you are trying to work them all out!

        // A null location, may shave off the tiniest fraction of CPU time, but probably not. Still, I like using this variable! :D
        private static readonly Vector3 vNullLocation = Vector3.Zero;

        // Used for a global bot-pause
        private static bool bMainBotPaused = false;

        // Used to force-refresh dia objects at least once every XX milliseconds 
        public static DateTime lastRefreshedObjects = DateTime.Today;

        // This object is used for the main handling - the "current target" etc. as selected by the target-selecter, whether it be a unit, an item, a shrine, anything
        // It's cached data using my own class, so I never need to hit D3 memory to "re-check" the data or to call an interact request or anything
        private static GilesObject targetCurrent = null;

        // A flag to indicate whether we have a new target from the overlord (decorator) or not, in which case don't refresh targets again this first loop
        private static bool bWholeNewTarget = false;

        // A flag to indicate if we should pick a new power/ability to use or not
        private static bool bPickNewAbilities = false;

        // Flag used to indicate if we are simply waiting for a power to go off - so don't do any new target checking or anything
        private static bool bWaitingForPower = false;
        // And a special post-use pause
        private static bool bWaitingAfterPower = false;

        // If we are waiting before popping a potion
        private static bool bWaitingForPotion = false;

        // Status text for DB main window status
        private static string sStatusText = "";

        // A flag to indicate if we just entered or just left archon form (and so to force-update the hotbar)
        private static bool bRefreshHotbarAbilities = false;
        private static bool bHasHadArchonbuff = false;

        // A "fake" object to send to target provider for stuck handlers etc.
        public static DiaObject thisFakeObject;

        // Timestamp of when our position was last measured as changed
        private static DateTime lastMovedDuringCombat = DateTime.Today;

        // The following three dictionaries are special caches - they start empty but fill up with data that gets re-used while the bot runs, saving on D3 memory hits
        // Monster type caching
        private static Dictionary<int, MonsterType> dictionaryStoredMonsterTypes = new Dictionary<int, MonsterType>();
        // Monster size caching
        private static Dictionary<int, MonsterSize> dictionaryStoredMonsterSizes = new Dictionary<int, MonsterSize>();

        // Temporarily ignore this object for *THREE* cycles
        private static int iIgnoreThisRactorGUID = 0;
        private static int iIgnoreThisForLoops = 0;

        // Holds all of the player's current info handily cached, updated once per loop with a minimum timer on updates to save D3 memory hits
        public static GilesPlayerCache playerStatus = new GilesPlayerCache(DateTime.Today, false, false, false, 0d, 0d, 0d, 0d, 0d, vNullLocation, false, 0, 1);

        // A list of obstacles for the navigation handler
        public static HashSet<GilesObstacle> hashNavigationObstacleCache = new HashSet<GilesObstacle>();

        // Related to the profile reloaded when restarting games, to pick the FIRST profile.
        // Also storing a list of all profiles, for experimental reasons/incase I want to use them down the line
        public static List<string> listProfilesLoaded = new List<string>();
        public static string sLastProfileSeen = "";
        public static string sFirstProfileSeen = "";

        // A list of small areas covering zones we move through while fighting to help our custom move-handler skip ahead waypoints
        public static HashSet<GilesObstacle> hashSkipAheadAreaCache = new HashSet<GilesObstacle>();
        public static DateTime lastAddedLocationCache = DateTime.Today;
        public static Vector3 vLastRecordedLocationCache = Vector3.Zero;
        public static bool bSkipAheadAGo = false;

        // A list of all monsters and their positions, so we don't try to walk through them during avoidance
        private static HashSet<GilesObstacle> hashMonsterObstacleCache = new HashSet<GilesObstacle>();
        // A list of all current obstacles, to help avoid running through them when picking targets
        private static HashSet<GilesObstacle> hashAvoidanceObstacleCache = new HashSet<GilesObstacle>();
        // Blacklist avoidance spots we failed to reach in time, for a period of time
        private static HashSet<GilesObstacle> hashAvoidanceBlackspot = new HashSet<GilesObstacle>();
        private static DateTime lastClearedAvoidanceBlackspots = DateTime.Today;

        // A count for player mystic ally, gargantuans, and zombie dogs
        private static int iPlayerOwnedMysticAlly = 0;
        private static int iPlayerOwnedGargantuan = 0;
        private static int iPlayerOwnedZombieDog = 0;
        private static int iPlayerOwnedDHPets = 0;

        // These are a bunch of safety counters for how many times in a row we register having *NO* ability to select when we need one (eg all off cooldown)
        // After so many, give the player a friendly warning to check their skill/build setup
        private static int iNoAbilitiesAvailableInARow = 0;
        private static DateTime lastRemindedAboutAbilities = DateTime.Today;

        // Prowl API Key
        public static string sProwlAPIKey = "";

        // Android API Key
        public static string sAndroidAPIKey = "";

        // Email Settings
        public static string sEmailAddress = "";
        public static string sEmailPassword = "";
        public static string sBotName = ZetaDia.Service.CurrentHero.BattleTagName;

        // Last had any mob in range, for loot-waiting
        private static DateTime lastHadUnitInSights = DateTime.Today;
        // When we last saw a boss/elite etc.
        private static DateTime lastHadEliteUnitInSights = DateTime.Today;

        // Do we need to reset the debug bar after combat handling?
        private static bool bResetStatusText = false;

        // A list of "useonceonly" tags that have been triggered this xml profile
        public static HashSet<int> hashUseOnceID = new HashSet<int>();
        public static Dictionary<int, int> dictUseOnceID = new Dictionary<int, int>();

        // For the random ID tag
        public static Dictionary<int, int> dictRandomID = new Dictionary<int, int>();

        // Death counts
        public static int iMaxDeathsAllowed = 0;
        public static int iDeathsThisRun = 0;

        // Force a target update after certain interactions
        private static bool bForceTargetUpdate = false;
        // This holds whether or not we want to prioritize a close-target, used when we might be body-blocked by monsters
        private static bool bForceCloseRangeTarget = false;
        // How many times a movement fails because of being "blocked"
        private static int iTimesBlockedMoving = 0;
        // how long to force close-range targets for
        private static int iMillisecondsForceCloseRange = 0;
        // Date time we were last told to stick to close range targets
        private static DateTime lastForcedKeepCloseRange = DateTime.Today;
        // The distance last loop, so we can compare to current distance to work out if we moved
        private static float iLastDistance = 0f;
        // Caching of the current primary target's health, to detect if we AREN'T damaging it for a period of time
        private static double iTargetLastHealth = 0f;
        // This is used so we don't use certain skills until we "top up" our primary resource by enough
        private static double iWaitingReservedAmount = 0d;
        // When did we last clear the temporary blacklist?
        private static DateTime dateSinceTemporaryBlacklistClear = DateTime.Today;
        // And the full blacklist?
        private static DateTime dateSinceBlacklistClear = DateTime.Today;
        // Store the date-time when we *FIRST* picked this target, so we can blacklist after X period of time targeting
        private static DateTime dateSincePickedTarget = DateTime.Today;
        // Total main loops so we can update things every XX loops
        private static int iCombatLoops = 0;
        // These values below are set on a per-class basis later on, so don't bother changing them here! These are the old default values
        private static double iEmergencyHealthPotionLimit = 0.46;
        private static double iEmergencyHealthGlobeLimit = 0.6;
        private static int iKiteDistance = 0;
        // These are blacklists used to blacklist objects/monsters/items either for the rest of the current game, or for a shorter period
        private static HashSet<int> hashRGUIDTemporaryIgnoreBlacklist = new HashSet<int>();
        private static HashSet<int> hashRGUIDIgnoreBlacklist = new HashSet<int>();
        // This is a blacklist that is cleared within 3 seconds of last attacking a destructible
        private static HashSet<int> hashRGUIDDestructibleBlacklist = new HashSet<int>();
        private static DateTime lastDestroyedDestructible = DateTime.Today;
        private static bool bNeedClearDestructibles = false;
        // This is a blacklist that is cleared within 3 seconds of last attacking a destructible
        private static HashSet<int> hashRGUIDTemporaryBlacklist = new HashSet<int>();
        private static DateTime lastTemporaryBlacklist = DateTime.Today;
        private static bool bNeedClearTemporaryBlacklist = false;
        // An ordered list of all of the backtrack locations to navigate through once we finish our current activities
        public static SortedList<int, Vector3> vBacktrackList = new SortedList<int, Vector3>();
        public static int iTotalBacktracks = 0;
        // The number of loops to extend kill range for after a fight to try to maximize kill bonus exp etc.
        private static int iKeepKillRadiusExtendedFor = 0;
        // The number of loops to extend loot range for after a fight to try to stop missing loot
        private static int iKeepLootRadiusExtendedFor = 0;
        // Some avoidance related variables
        // Whether or not we need avoidance this target-search-loop
        private static bool bRequireAvoidance = false;
        // Whether or not there are projectiles to avoid
        private static bool bTravellingAvoidance = false;
        // When we last FOUND a safe spot
        private static DateTime lastFoundSafeSpot = DateTime.Today;
        private static Vector3 vlastSafeSpot = Vector3.Zero;
        // This lets us know if there is a target but it's in avoidance so we can just "stay put" until avoidance goes
        private static bool bStayPutDuringAvoidance = false;
        // This force-prevents avoidance for XX loops incase we get stuck trying to avoid stuff
        private static DateTime timeCancelledEmergencyMove = DateTime.Today;
        private static int iMillisecondsCancelledEmergencyMoveFor = 0;
        // Prevent spam-kiting too much - allow fighting between each kite movement
        private static DateTime timeCancelledKiteMove = DateTime.Today;
        private static int iMillisecondsCancelledKiteMoveFor = 0;
        // For if we have emergency teleport abilities available right now or not
        private static bool bHasEmergencyTeleportUp = false;

        // How many follower items were ignored, purely for item stat tracking
        private static int iTotalFollowerItemsIgnored = 0;

        // Variable to let us force new target creations immediately after a root
        private static bool bWasRootedLastTick = false;

        // Random variables used during item handling and town-runs
        private static int iItemDelayLoopLimit = 0;
        private static int iCurrentItemLoops = 0;
        private static bool bLoggedAnythingThisStash = false;
        private static bool bUpdatedStashMap = false;
        private static bool bLoggedJunkThisStash = false;
        private static string sValueItemStatString = "";
        private static string sJunkItemStatString = "";
        private static bool bTestingBackpack = false;

        // Stash mapper - it's an array representing every slot in your stash, true or false dictating if the slot is free or not
        private static bool[,] GilesStashSlotBlocked = new bool[7, 30];

        private static bool bOutputItemScores = false;
        // Full Analysis SPAMS like hell. But useful for seeing how the score-calculator is adding/removing points
        // Really this is only for Giles to debug and improve the formula, users likely won't find this useful
        private const bool bFullAnalysis = false; // Teehee I typed "Anal" LOLOLOL

        // Variables used to actually hold powers the power-selector has picked to use, for buffing and main power use
        private static GilesPower powerBuff;
        private static GilesPower powerPrime;
        private static SNOPower powerLastSnoPowerUsed = SNOPower.None;

        // Two variables to stop DB from attempting any navigator movement mid-combat/mid-backtrack
        public static bool bDontMoveMeIAmDoingShit = false;
        public static bool bDontSpamOutofCombat = false;
        public static bool bOnlyTarget = false;
        // Target provider and core routine variables
        public static string sTrinityPluginPath = "";
        private static string sTrinityConfigFile = "";
        private static string sTrinityEmailConfigFile = "";
        private static bool bSavingConfig = false;
        public static ActorClass iMyCachedActorClass = ActorClass.Invalid;
        private static bool bAnyChampionsPresent = false;
        private static bool bAnyTreasureGoblinsPresent = false;
        private static bool bAnyMobsInCloseRange = false;
        private static float iCurrentMaxKillRadius = 0f;
        private static float iCurrentMaxLootRadius = 0f;

        // Goblinney things
        private static bool bUseBerserker = false;
        private static bool bWaitingForSpecial = false;
        private static int iTotalNumberGoblins = 0;
        private static DateTime lastGoblinTime = DateTime.Today;

        private static DateTime SweepWindSpam = DateTime.Today; //intell -- inna

        // Variables relating to quick-reference of monsters within sepcific ranges (if anyone has suggestion for similar functionality with reduced CPU use, lemme know, but this is fast atm!)
        private static int[] iElitesWithinRange = new int[] { 0, 0, 0, 0, 0, 0, 0 };
        private static int[] iAnythingWithinRange = new int[] { 0, 0, 0, 0, 0, 0, 0 };
        private static bool bAnyBossesInRange = false;
        private const int RANGE_50 = 0;
        private const int RANGE_40 = 1;
        private const int RANGE_30 = 2;
        private const int RANGE_25 = 3;
        private const int RANGE_20 = 4;
        private const int RANGE_15 = 5;
        private const int RANGE_12 = 6;
        private const int RANGE_6 = 7;
        private static int iWithinRangeLastRend = 0;
        // Unique ID of mob last targetting when using rend
        private static int iACDGUIDLastRend = 0;
        // Unique ID of mob last targetting when using whirlwind
        private static int iACDGUIDLastWhirlwind = 0;

        private static bool bAlreadyMoving = false;
        private static Vector3 vLastMoveToTarget;
        private static float fLastDistanceFromTarget;
        private static DateTime lastMovementCommand = DateTime.Today;

        // Actual combat function variables
        private static bool bMappedPlayerAbilities = false;
        // Contains our apparent *CURRENT* hotbar abilities, cached in a fast hash
        public static HashSet<SNOPower> hashPowerHotbarAbilities = new HashSet<SNOPower>();
        // Contains a hash of our LAST hotbar abilities before we transformed into archon (for quick and safe hotbar restoration)
        public static HashSet<SNOPower> hashCachedPowerHotbarAbilities = new HashSet<SNOPower>();
        // A list and a dictionary for quick buff checking and buff references
        private static List<Buff> listCachedBuffs = new List<Buff>();
        private static Dictionary<int, int> dictCachedBuffs = new Dictionary<int, int>();

        // For "position-shifting" to navigate around obstacle SNO's
        private static Vector3 vShiftedPosition = Vector3.Zero;
        private static DateTime lastShiftedPosition = DateTime.Today;
        private static int iShiftPositionFor = 0;

        private static Vector3 vCurrentDestination;
        private static Vector3 vSideToSideTarget;
        private static DateTime lastChangedZigZag = DateTime.Today;
        private static Vector3 vPositionLastZigZagCheck = Vector3.Zero;
        public static int iCurrentWorldID = -1;
        public static GameDifficulty iCurrentGameDifficulty = GameDifficulty.Invalid;

        private const bool USE_COMBAT_ONLY = false;
        private const bool USE_ANY_TIME = true;
        private const bool SIGNATURE_SPAM = false;
        private const bool USE_SLOWLY = true;

        // Constants and variables used by the item-stats stuff
        private const int QUALITYWHITE = 0;
        private const int QUALITYBLUE = 1;
        private const int QUALITYYELLOW = 2;
        private const int QUALITYORANGE = 3;
        private static readonly string[] sQualityString = new string[4] { "White", "Magic", "Rare", "Legendary" };
        private const int GEMRUBY = 0;
        private const int GEMTOPAZ = 1;
        private const int GEMAMETHYST = 2;
        private const int GEMEMERALD = 3;
        private static readonly string[] sGemString = new string[4] { "Ruby", "Topaz", "Amethyst", "Emerald" };
        private static DateTime ItemStatsLastPostedReport = DateTime.Now;
        private static DateTime ItemStatsWhenStartedBot = DateTime.Now;
        private static bool bMaintainStatTracking = false;

        // Store items already logged by item-stats, to make sure no stats get doubled up by accident
        private static HashSet<int> _hashsetItemStatsLookedAt = new HashSet<int>();
        private static HashSet<int> _hashsetItemPicksLookedAt = new HashSet<int>();
        private static HashSet<int> _hashsetItemFollowersIgnored = new HashSet<int>();

        // This dictionary stores attempted stash counts on items, to help detect any stash stucks on the same item etc.
        private static Dictionary<int, int> _dictItemStashAttempted = new Dictionary<int, int>();

        // These objects are instances of my stats class above, holding identical types of data for two different things - one holds item DROP stats, one holds item PICKUP stats
        private static GilesItemStats ItemsDroppedStats = new GilesItemStats(0, new double[4], new double[64], new double[4, 64], 0, new double[64], 0, new double[4], new double[64], new double[4, 64], 0);
        private static GilesItemStats ItemsPickedStats = new GilesItemStats(0, new double[4], new double[64], new double[4, 64], 0, new double[64], 0, new double[4], new double[64], new double[4, 64], 0);
        private static DateTime TickInformation = DateTime.Now;

        // These constants are used for item scoring and stashing
        private const int DEXTERITY = 0;
        private const int INTELLIGENCE = 1;
        private const int STRENGTH = 2;
        private const int VITALITY = 3;
        private const int LIFEPERCENT = 4;
        private const int LIFEONHIT = 5;
        private const int LIFESTEAL = 6;
        private const int LIFEREGEN = 7;
        private const int MAGICFIND = 8;
        private const int GOLDFIND = 9;
        private const int MOVEMENTSPEED = 10;
        private const int PICKUPRADIUS = 11;
        private const int SOCKETS = 12;
        private const int CRITCHANCE = 13;
        private const int CRITDAMAGE = 14;
        private const int ATTACKSPEED = 15;
        private const int MINDAMAGE = 16;
        private const int MAXDAMAGE = 17;
        private const int BLOCKCHANCE = 18;
        private const int THORNS = 19;
        private const int ALLRESIST = 20;
        private const int RANDOMRESIST = 21;
        private const int TOTALDPS = 22;
        private const int ARMOR = 23;
        private const int MAXDISCIPLINE = 24;
        private const int MAXMANA = 25;
        private const int ARCANECRIT = 26;
        private const int MANAREGEN = 27;
        private const int GLOBEBONUS = 28;

        private const int TOTALSTATS = 29; // starts at 0, remember... 0-26 = 1-27!

        // Readable names of the above stats that get output into the trash/stash log files
        private static readonly string[] StatNames = new string[29] { "Dexterity", "Intelligence", "Strength", "Vitality", "Life %", "Life On Hit", "Life Steal %", "Life Regen", 
            "Magic Find %", "Gold Find   %", "Movement Speed %", "Pickup Radius", "Sockets", "Crit Chance %", "Crit Damage %", "Attack Speed %", "+Min Damage", 
            "+Max Damage", "Total Block %", "Thorns", "+All Resist", "+Highest Single Resist", "DPS", "Armor", "Max Disc.", "Max Mana", "Arcane-On-Crit", "Mana Regen", "Globe Bonus"};
        // Stores the apparent maximums of each stat for each item slot
        // Note that while these SHOULD be *actual* maximums for most stats - for things like DPS, these can just be more sort of "what a best-in-slot DPS would be"
        //												                Dex  Int  Str  Vit  Life%     LOH Steal%  LPS Magic% Gold% MSPD Rad. Sox Crit% CDam% ASPD Min+ Max+ Block% Thorn Allres Res   DPS ARMOR Disc.Mana Arc. Regen  Globes
        private static double[] iMaxWeaponOneHand = new double[29] { 320, 320, 320, 320, 0, 850, 2.8, 0, 0, 0, 0, 0, 1, 0, 85, 0, 0, 0, 0, 0, 0, 0, 1210, 0, 10, 150, 10, 14, 0 };
        private static double[] iMaxWeaponTwoHand = new double[29] { 530, 530, 530, 530, 0, 1800, 5.8, 0, 0, 0, 0, 0, 1, 0, 170, 0, 0, 0, 0, 0, 0, 0, 1590, 0, 10, 119, 10, 14, 0 };
        private static double[] iMaxWeaponRanged = new double[29] { 320, 320, 320, 320, 0, 850, 2.8, 0, 0, 0, 0, 0, 1, 0, 85, 0, 0, 0, 0, 0, 0, 0, 1410, 0, 0, 0, 0, 14, 0 };
        private static double[] iMaxOffHand = new double[29] { 300, 300, 300, 300, 9, 0, 0, 234, 18, 20, 0, 0, 1, 8.5, 0, 15, 110, 402, 0, 979, 0, 0, 0, 0, 10, 119, 10, 11, 5977 };
        private static double[] iMaxShield = new double[29] { 330, 330, 330, 330, 16, 0, 0, 342, 20, 25, 0, 0, 1, 10, 0, 0, 0, 0, 30, 2544, 80, 60, 0, 397, 0, 0, 0, 0, 12794 };
        private static double[] iMaxRing = new double[29] { 200, 200, 200, 200, 12, 479, 0, 234, 20, 25, 0, 0, 1, 6, 50, 9, 36, 100, 0, 979, 80, 60, 0, 240, 0, 0, 0, 0, 5977 };
        private static double[] iMaxAmulet = new double[29] { 350, 350, 350, 350, 16, 959, 0, 410, 45, 50, 0, 0, 1, 10, 100, 9, 36, 100, 0, 1712, 80, 60, 0, 360, 0, 0, 0, 0, 5977 };
        private static double[] iMaxShoulders = new double[29] { 200, 200, 300, 200, 12, 0, 0, 342, 20, 25, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2544, 80, 60, 0, 265, 0, 0, 0, 0, 12794 };
        private static double[] iMaxHelm = new double[29] { 200, 300, 200, 200, 12, 0, 0, 342, 20, 25, 0, 7, 1, 6, 0, 0, 0, 0, 0, 1454, 80, 60, 0, 397, 0, 0, 0, 0, 12794 };
        private static double[] iMaxPants = new double[29] { 200, 200, 200, 300, 0, 0, 0, 342, 20, 25, 0, 7, 2, 0, 0, 0, 0, 0, 0, 1454, 80, 60, 0, 397, 0, 0, 0, 0, 12794 };
        private static double[] iMaxGloves = new double[29] { 300, 300, 200, 200, 0, 0, 0, 342, 20, 25, 0, 7, 0, 10, 50, 9, 0, 0, 0, 1454, 80, 60, 0, 265, 0, 0, 0, 0, 12794 };
        private static double[] iMaxChest = new double[29] { 200, 200, 200, 300, 12, 0, 0, 599, 20, 25, 0, 7, 3, 0, 0, 0, 0, 0, 0, 2544, 80, 60, 0, 397, 0, 0, 0, 0, 12794 };
        private static double[] iMaxBracer = new double[29] { 200, 200, 200, 200, 0, 0, 0, 342, 20, 25, 0, 7, 0, 6, 0, 0, 0, 0, 0, 1454, 80, 60, 0, 265, 0, 0, 0, 0, 12794 };
        private static double[] iMaxBoots = new double[29] { 300, 200, 200, 200, 0, 0, 0, 342, 20, 25, 12, 7, 0, 0, 0, 0, 0, 0, 0, 1454, 80, 60, 0, 265, 0, 0, 0, 0, 12794 };
        private static double[] iMaxBelt = new double[29] { 200, 200, 300, 200, 12, 0, 0, 342, 20, 25, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2544, 80, 60, 0, 265, 0, 0, 0, 0, 12794 };
        private static double[] iMaxCloak = new double[29] { 200, 200, 200, 300, 12, 0, 0, 410, 20, 25, 0, 7, 3, 0, 0, 0, 0, 0, 0, 2544, 70, 50, 0, 397, 10, 0, 0, 0, 12794 };
        private static double[] iMaxMightyBelt = new double[29] { 200, 200, 300, 200, 12, 0, 3, 342, 20, 25, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2544, 70, 50, 0, 265, 0, 0, 0, 0, 12794 };
        private static double[] iMaxSpiritStone = new double[29] { 200, 300, 200, 200, 12, 0, 0, 342, 20, 25, 0, 7, 1, 6, 0, 0, 0, 0, 0, 1454, 70, 50, 0, 397, 0, 0, 0, 0, 12794 };
        private static double[] iMaxVoodooMask = new double[29] { 200, 300, 200, 200, 12, 0, 0, 342, 20, 25, 0, 7, 1, 6, 0, 0, 0, 0, 0, 1454, 70, 50, 0, 397, 0, 119, 0, 11, 12794 };
        private static double[] iMaxWizardHat = new double[29] { 200, 300, 200, 200, 12, 0, 0, 342, 20, 25, 0, 7, 1, 6, 0, 0, 0, 0, 0, 1454, 70, 50, 0, 397, 0, 0, 10, 0, 12794 };
        private static double[] iMaxFollower = new double[29] { 300, 300, 300, 200, 0, 300, 0, 234, 0, 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 50, 40, 0, 0, 0, 0, 0, 0, 0 };

        // Stores the total points this stat is worth at the above % point of maximum
        // Note that these values get all sorts of bonuses, multipliers, and extra things applied in the actual scoring routine. These values are more of a "base" value.
        //                                                              Dex    Int    Str    Vit    Life%  LOH    Steal% LPS   Magic%  Gold%  MSPD   Rad  Sox    Crit%  CDam%  ASPD   Min+  Max+ Block% Thorn Allres Res   DPS    ARMOR  Disc.  Mana  Arc.  Regen  Globes
        private static double[] iWeaponPointsAtMax = new double[29] { 14000, 14000, 14000, 14000, 13000, 20000, 7000, 1000, 6000, 6000, 6000, 500, 16000, 15000, 15000, 0, 0, 0, 0, 1000, 11000, 0, 64000, 0, 10000, 8500, 8500, 10000, 8000 };
        //                                                              Dex    Int    Str    Vit    Life%  LOH    Steal% LPS   Magic%  Gold%  MSPD   Rad. Sox    Crit%  CDam%  ASPD   Min+  Max+ Block% Thorn Allres Res   DPS    ARMOR  Disc.  Mana  Arc.  Regen  Globes
        private static double[] iArmorPointsAtMax = new double[29] { 11000, 11000, 11000, 9500, 9000, 10000, 4000, 1200, 3000, 3000, 3500, 1000, 4300, 9000, 6100, 7000, 3000, 3000, 5000, 1200, 7500, 1500, 0, 5000, 4000, 3000, 3000, 6000, 5000 };
        private static double[] iJewelryPointsAtMax = new double[29] { 11500, 11000, 11000, 10000, 8000, 11000, 4000, 1200, 4500, 4500, 3500, 1000, 3500, 7500, 6300, 6800, 800, 800, 5000, 1200, 7500, 1500, 0, 4500, 4000, 3000, 3000, 6000, 5000 };

        // Some special values for score calculations
        // BonusThreshold is a percentage of the "max-stat possible", that the stat starts to get a multiplier on it's score. 1 means it has to be above 100% of the "max-stat" to get a multiplier (so only possible if the max-stat isn't ACTUALLY the max possible)
        // MinimumThreshold is a percentage of the "max-stat possible", that the stat will simply be ignored for being too low. eg if set to .5 - then anything less than 50% of the max-stat will be ignored.
        // MinimumPrimary is used for some stats only - and means that at least ONE primary stat has to be above that level, to get score. Eg magic-find has .5 - meaning any item without at least 50% of a max-stat primary, will ignore magic-find scoring.
        //                                                             Dex  Int  Str  Vit  Life%  LOH  Steal%   LPS Magic% Gold% MSPD Radi  Sox  Crit% CDam% ASPD  Min+  Max+  Block%  Thorn  Allres  Res   DPS  ARMOR   Disc. Mana  Arc. Regen  Globes
        private static double[] iBonusThreshold = new double[29] { .75, .75, .75, .75, .80, .80, .9, 1, 1, 1, .95, 1, 1, .70, .90, 1, .9, .9, .83, 1, .85, .95, .90, .90, 1, 1, 1, .9, 1 };
        private static double[] iMinimumThreshold = new double[29] { .40, .40, .40, .30, .60, .45, .7, .7, .64, .64, .75, .8, .4, .40, .60, .40, .2, .2, .65, .6, .40, .55, .40, .80, .7, .7, .7, .7, .8 };
        private static double[] iStatMinimumPrimary = new double[29] { 0, 0, 0, 0, 0, 0, 0, .2, .50, .50, .30, 0, 0, 0, 0, 0, .40, .40, .40, .40, .40, .40, 0, .40, .40, .40, .40, .4, .4 };

        // These three lists are used to cache item data from the backpack when handling sales, salvaging and stashing
        // It completely minimized D3 <-> DB memory access, to reduce any random bugs/crashes etc.
        private static HashSet<GilesCachedACDItem> hashGilesCachedKeepItems = new HashSet<GilesCachedACDItem>();
        private static HashSet<GilesCachedACDItem> hashGilesCachedSalvageItems = new HashSet<GilesCachedACDItem>();
        private static HashSet<GilesCachedACDItem> hashGilesCachedSellItems = new HashSet<GilesCachedACDItem>();
        // Whether to try forcing a vendor-run for custom reasons
        public static bool bGilesForcedVendoring = false;
        public static bool bWantToTownRun = false;
        private static bool bLastTownRunCheckResult = false;
        // Whether salvage/sell run should go to a middle-waypoint first to help prevent stucks
        private static bool bGoToSafetyPointFirst = false;
        private static bool bGoToSafetyPointSecond = false;
        private static bool bReachedSafety = false;
        // DateTime check to prevent inventory-check spam when looking for repairs being needed
        private static DateTime TimeLastCheckedForTownRun = DateTime.Now;
        private static bool bCurrentlyMoving = false;
        private static bool bReachedDestination = false;
        private static bool bNeedsEquipmentRepairs = false;
        private static float iLowestDurabilityFound = -1;

        // Bunch of temporary variables that get used when creating the current object/target list - this was just a nicer way for me to handle it code wise at first
        // Even if it looks a bit messy and probably should have just used it's own object instance of the cache-class instead! :D
        private static Vector3 tmp_vThisPosition = Vector3.Zero;
        private static GilesObjectType tmp_ThisGilesObjectType = GilesObjectType.Unknown;
        private static double tmp_dThisWeight = 0d;
        private static float tmp_fCentreDistance = 0f;
        private static float tmp_fRadiusDistance = 0f;
        private static string tmp_sThisInternalName = "";
        private static int tmp_iThisACDGUID = 0;
        private static int tmp_iThisRActorGuid = 0;
        private static int tmp_iThisDynamicID = 0;
        private static int tmp_iThisBalanceID = 0;
        private static int tmp_iThisActorSNO = 0;
        private static int tmp_item_iThisLevel = 0;
        private static int tmp_item_iThisGoldAmount = 0;
        private static bool tmp_item_bThisOneHanded = false;
        private static ItemQuality tmp_item_ThisQuality = ItemQuality.Invalid;
        private static ItemType tmp_item_ThisDBItemType = ItemType.Unknown;
        private static FollowerType tmp_item_ThisFollowerType = FollowerType.None;
        private static GilesItemType tmp_item_ThisGilesItemType = GilesItemType.Unknown;
        private static bool tmp_unit_bThisElite = false;
        private static bool tmp_unit_bThisRare = false;
        private static bool tmp_unit_bThisUnique = false;
        private static bool tmp_unit_bThisMinion = false;
        private static bool tmp_unit_bThisTreasureGoblin = false;
        private static bool tmp_bThisEliteRareUnique = false;
        private static bool tmp_unit_bThisBoss = false;
        private static bool tmp_unit_bThisAttackable = false;
        private static bool tmp_bForceLeapAgainst = false;
        private static double tmp_unit_iThisHitPoints = 0d;
        private static float tmp_fThisRadius = 0f;
        private static MonsterSize tmp_unit_ThisMonsterSize = MonsterSize.Unknown;
        private static DiaUnit tmp_unit_diaUnit = null;

        // **********************************************************************************************
        // *****                     Special Sauce Dictionaries for SPEEEEEEED                      *****
        // **********************************************************************************************

        // This set of dictionaries are used for huge performance increases throughout, and a minimization of DB mis-read/null exception errors
        // Uses a little more ram - but for a massive CPU gain. And ram is cheap, CPU is not!

        // The following 2 variables are used to clear the dictionaries out - clearing one dictionary out per maximum every 2 seconds, working through in sequential order
        private static DateTime lastClearedCacheDictionary = DateTime.Today;
        private static int iLastClearedCacheDictionary = 0;

        // Caches the GilesObjectType of each object as we find it (RactorGUID based)
        private static Dictionary<int, GilesObjectType> dictGilesObjectTypeCache = new Dictionary<int, GilesObjectType>();

        // Caches monster affixes for the monster ID, as this value can be a pain to get and slow (RactorGUID based)
        private static Dictionary<int, MonsterAffixes> dictGilesMonsterAffixCache = new Dictionary<int, MonsterAffixes>();

        // Caches each monster's max-health, since this never changes (RactorGUID based)
        private static Dictionary<int, double> dictGilesMaxHealthCache = new Dictionary<int, double>();

        // Caches each monster's current health for brief periods  (RactorGUID based)
        private static Dictionary<int, double> dictGilesLastHealthCache = new Dictionary<int, double>();
        private static Dictionary<int, int> dictGilesLastHealthChecked = new Dictionary<int, int>();

        // Store a "not-burrowed" value for monsters that we have already checked a burrowed-status of and found false (RactorGUID based)
        private static Dictionary<int, bool> dictGilesBurrowedCache = new Dictionary<int, bool>();

        // Store Actor SNO for each object (RactorGUID based)
        private static Dictionary<int, int> dictGilesActorSNOCache = new Dictionary<int, int>();

        // Store ACDGUID for each object (RactorGUID based)
        private static Dictionary<int, int> dictGilesACDGUIDCache = new Dictionary<int, int>();

        // Store internal name for each object (RactorGUID based)
        private static Dictionary<int, string> dictGilesInternalNameCache = new Dictionary<int, string>();

        // Store Collision-sphere radius for each object (SNO based)
        private static Dictionary<int, float> dictGilesCollisionSphereCache = new Dictionary<int, float>();

        // Caches the game balance ID for each object, which can then be used to pull up the appropriate entry from within dictGilesGameBalanceDataCache (RactorGUID based)
        private static Dictionary<int, int> dictGilesGameBalanceIDCache = new Dictionary<int, int>();

        // Caches the Dynamic ID for each object (only used for non-units) (RactorGUID based)
        private static Dictionary<int, int> dictGilesDynamicIDCache = new Dictionary<int, int>();

        // Caches the position for each object (only used for non-units, as this data is static so can be cached) (RactorGUID based)
        private static Dictionary<int, Vector3> dictGilesVectorCache = new Dictionary<int, Vector3>();

        // Same as above but for gold-amount of pile (RactorGUID based)
        private static Dictionary<int, int> dictGilesGoldAmountCache = new Dictionary<int, int>();

        // Same as above but for quality of item, we check this twice to make bloody sure we don't miss a legendary from a mis-read though (RactorGUID based)
        private static Dictionary<int, ItemQuality> dictGilesQualityCache = new Dictionary<int, ItemQuality>();
        private static Dictionary<int, bool> dictGilesQualityRechecked = new Dictionary<int, bool>();

        // Same as above but for whether we want to pick it up or not (RactorGUID based)
        private static Dictionary<int, bool> dictGilesPickupItem = new Dictionary<int, bool>();

        // How many times the player tried to interact with this object in total
        private static Dictionary<int, int> dictTotalInteractionAttempts = new Dictionary<int, int>();

        // Physics SNO for certain objects (SNO based)
        private static Dictionary<int, int> dictPhysicsSNO = new Dictionary<int, int>();

        // Summoned-by ID for units (RactorGUID based)
        private static Dictionary<int, int> dictSummonedByID = new Dictionary<int, int>();

        // **********************************************************************************************
        // *****                        Giant Super Special Sauce Dictionary                        *****
        // **********************************************************************************************

        // Here's the a huuuuuge dictionary I have been building up containing cached data on items to minimize D3 memory reads and help prevent some DB mis-handling of items
        // Note that even if an item is not on the list - it will add it to the cache "temporarily" (lasting for the duration of the bot run) - so if your bot encounters the 
        // same kind of item twice in a row - the second time, it will use the cached data - so it will still minimize issues even if I haven't added all items to this list :D

        private static Dictionary<int, GilesGameBalanceDataCache> dictGilesGameBalanceDataCache = new Dictionary<int, GilesGameBalanceDataCache>
        #region GameBalanceIDCache
        {
{-1106917318, new GilesGameBalanceDataCache(1, ItemType.CraftingPage, false, FollowerType.None)}, //Lore_Book_Flippy-10246    - Could be an error item!?!?
{181033993, new GilesGameBalanceDataCache(62, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_07-946
{-970366835, new GilesGameBalanceDataCache(61, ItemType.CraftingPage, false, FollowerType.None)}, //Lore_Book_Flippy-898
{126259833, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldSmall-914
{126259831, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldCoin-1881
{126259832, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldCoins-1007
{126259834, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldMedium-657
{126259835, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldLarge-7981
{-1483610851, new GilesGameBalanceDataCache(60, ItemType.Potion, false, FollowerType.None)}, //healthPotion_Mythic-776
{-1962741247, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //HealthGlobe-580
{1661414572, new GilesGameBalanceDataCache(61, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_05-495
{-1533912123, new GilesGameBalanceDataCache(61, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-1995
{-330720411, new GilesGameBalanceDataCache(63, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_07-1580
{-733829188, new GilesGameBalanceDataCache(61, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-1696
{2140882331, new GilesGameBalanceDataCache(60, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-1701
{-1616888606, new GilesGameBalanceDataCache(55, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_02-1697
{1565456762, new GilesGameBalanceDataCache(60, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-3182
{2140882332, new GilesGameBalanceDataCache(61, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-3189
{-2115689173, new GilesGameBalanceDataCache(62, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_06-3325
{-1962741209, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //HealthGlobe_02-3320
{2140882334, new GilesGameBalanceDataCache(63, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-3495
{40857596, new GilesGameBalanceDataCache(56, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-3571
{2058771892, new GilesGameBalanceDataCache(54, ItemType.Gem, false, FollowerType.None)}, //Topaz_07-3299
{-1303413119, new GilesGameBalanceDataCache(62, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_06-3766
{-2115689174, new GilesGameBalanceDataCache(61, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_05-3744
{1565456763, new GilesGameBalanceDataCache(61, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-3738
{290068679, new GilesGameBalanceDataCache(57, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_01-3850
{-1533912124, new GilesGameBalanceDataCache(60, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-3917
{88667232, new GilesGameBalanceDataCache(61, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_05-4365
{1146967348, new GilesGameBalanceDataCache(62, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-4767
{-1512729955, new GilesGameBalanceDataCache(63, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-4731
{-1303413123, new GilesGameBalanceDataCache(55, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_02-4517
{1700549963, new GilesGameBalanceDataCache(61, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_03-4584
{-2115689176, new GilesGameBalanceDataCache(57, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_03-4601
{-335464095, new GilesGameBalanceDataCache(59, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_03-4602
{-733830276, new GilesGameBalanceDataCache(52, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-4600
{620036246, new GilesGameBalanceDataCache(61, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-4950
{-1616888603, new GilesGameBalanceDataCache(62, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_05-4944
{1682228653, new GilesGameBalanceDataCache(62, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-5681
{-733829189, new GilesGameBalanceDataCache(60, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-6048
{-1303413121, new GilesGameBalanceDataCache(59, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_04-1383
{1815809035, new GilesGameBalanceDataCache(55, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_01-2564
{1565456765, new GilesGameBalanceDataCache(63, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-3402
{1603007817, new GilesGameBalanceDataCache(60, ItemType.Gem, false, FollowerType.None)}, //Ruby_08-3158
{1236607149, new GilesGameBalanceDataCache(61, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_02-3659
{1146967346, new GilesGameBalanceDataCache(60, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-3918
{-1337761336, new GilesGameBalanceDataCache(62, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_07-4052
{181033988, new GilesGameBalanceDataCache(53, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_02-4078
{-1337761340, new GilesGameBalanceDataCache(56, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_03-5687
{2140882330, new GilesGameBalanceDataCache(58, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-5685
{1565456761, new GilesGameBalanceDataCache(58, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-5684
{-1533912125, new GilesGameBalanceDataCache(58, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-6368
{-270936739, new GilesGameBalanceDataCache(59, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_03-6486
{-1337761339, new GilesGameBalanceDataCache(58, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_04-7960
{-2091501889, new GilesGameBalanceDataCache(63, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_06-7966
{-101310578, new GilesGameBalanceDataCache(58, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_02-7962
{1755623811, new GilesGameBalanceDataCache(62, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-8781
{1236607148, new GilesGameBalanceDataCache(60, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_03-8789
{-1733388799, new GilesGameBalanceDataCache(60, ItemType.Gem, false, FollowerType.None)}, //Emerald_08-10718
{1682228651, new GilesGameBalanceDataCache(60, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-10727
{-136815383, new GilesGameBalanceDataCache(53, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_04-10810
{1700549962, new GilesGameBalanceDataCache(59, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_02-10812
{2112157586, new GilesGameBalanceDataCache(61, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-11311
{-1533912121, new GilesGameBalanceDataCache(63, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-11314
{181033989, new GilesGameBalanceDataCache(54, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_03-12195
{2140882329, new GilesGameBalanceDataCache(55, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-12217
{-1303413122, new GilesGameBalanceDataCache(57, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_03-13817
{-1303413120, new GilesGameBalanceDataCache(61, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_05-14074
{-270936738, new GilesGameBalanceDataCache(61, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_04-14072
{-1337761337, new GilesGameBalanceDataCache(61, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_06-14964
{365492431, new GilesGameBalanceDataCache(62, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-1437
{1565456764, new GilesGameBalanceDataCache(62, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-1315
{-101310577, new GilesGameBalanceDataCache(61, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_03-3318
{-1512729959, new GilesGameBalanceDataCache(58, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-7736
{-875942695, new GilesGameBalanceDataCache(63, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_06-8312
{-2015049108, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-11075
{-733829186, new GilesGameBalanceDataCache(63, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-11066
{2140882333, new GilesGameBalanceDataCache(62, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-11993
{1539238478, new GilesGameBalanceDataCache(56, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-12027
{-733829187, new GilesGameBalanceDataCache(62, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-13327
{-231801347, new GilesGameBalanceDataCache(61, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_05-14009
{-331906332, new GilesGameBalanceDataCache(62, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_06-15167
{-1616888604, new GilesGameBalanceDataCache(61, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_04-16886
{-875942700, new GilesGameBalanceDataCache(55, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_01-668
{-1337761335, new GilesGameBalanceDataCache(63, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_08-1354
{1700549964, new GilesGameBalanceDataCache(62, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_04-1346
{-363389486, new GilesGameBalanceDataCache(61, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_06-1802
{-1512729958, new GilesGameBalanceDataCache(60, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-3342
{-2115689175, new GilesGameBalanceDataCache(59, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_04-3662
{-1411866890, new GilesGameBalanceDataCache(60, ItemType.Gem, false, FollowerType.None)}, //Amethyst_08-3681
{2058771893, new GilesGameBalanceDataCache(60, ItemType.Gem, false, FollowerType.None)}, //Topaz_08-3683
{-875942698, new GilesGameBalanceDataCache(60, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_03-5860
{1565456760, new GilesGameBalanceDataCache(55, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-7297
{-1512729957, new GilesGameBalanceDataCache(61, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-8321
{-875942699, new GilesGameBalanceDataCache(58, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_02-9464
{-1337761341, new GilesGameBalanceDataCache(54, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_02-9531
{-363389487, new GilesGameBalanceDataCache(60, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_05-9530
{-229899869, new GilesGameBalanceDataCache(57, ItemType.FollowerSpecial, false, FollowerType.Scoundrel)}, //JewelBox_Flippy-10528
{-733829191, new GilesGameBalanceDataCache(55, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-10549
{1146967347, new GilesGameBalanceDataCache(61, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-10553
{1612259883, new GilesGameBalanceDataCache(58, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-13213
{1682228652, new GilesGameBalanceDataCache(61, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-13904
{-101310576, new GilesGameBalanceDataCache(62, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_04-14801
{-333092253, new GilesGameBalanceDataCache(61, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_05-15478
{-875942696, new GilesGameBalanceDataCache(62, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_05-2766
{1661414569, new GilesGameBalanceDataCache(56, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_02-4779
{-363389485, new GilesGameBalanceDataCache(62, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_07-6008
{2140881244, new GilesGameBalanceDataCache(52, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-6020
{290068680, new GilesGameBalanceDataCache(61, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_02-6907
{365492432, new GilesGameBalanceDataCache(63, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-7287
{-1656025083, new GilesGameBalanceDataCache(51, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_07-14357
{-635267403, new GilesGameBalanceDataCache(60, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_03-14369
{-242893289, new GilesGameBalanceDataCache(60, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-17237
{1539238479, new GilesGameBalanceDataCache(59, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-17846
{1612259885, new GilesGameBalanceDataCache(61, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-4464
{1612259886, new GilesGameBalanceDataCache(62, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-7114
{290068681, new GilesGameBalanceDataCache(62, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_03-7147
{-1616888605, new GilesGameBalanceDataCache(58, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_03-7560
{1146967345, new GilesGameBalanceDataCache(57, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-7571
{-2091501894, new GilesGameBalanceDataCache(52, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_01-7570
{1612259882, new GilesGameBalanceDataCache(55, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-7569
{1661414570, new GilesGameBalanceDataCache(58, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_03-7578
{-363389488, new GilesGameBalanceDataCache(58, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_04-9381
{1147341802, new GilesGameBalanceDataCache(60, ItemType.FollowerSpecial, false, FollowerType.Templar)}, //JewelBox_Flippy-10391
{-1512729960, new GilesGameBalanceDataCache(55, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-13909
{365492429, new GilesGameBalanceDataCache(60, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-13913
{-101310575, new GilesGameBalanceDataCache(63, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_05-15926
{88667230, new GilesGameBalanceDataCache(58, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_03-17386
{-1656023995, new GilesGameBalanceDataCache(62, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_06-17434
{-363389490, new GilesGameBalanceDataCache(54, ItemType.HandCrossbow, true, FollowerType.None)}, //handXbow_norm_base_flippy_02-19180
{365491342, new GilesGameBalanceDataCache(52, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-19272
{365492430, new GilesGameBalanceDataCache(61, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-20609
{-136814295, new GilesGameBalanceDataCache(61, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_03-24992
{-1512729956, new GilesGameBalanceDataCache(62, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-3581
{181033991, new GilesGameBalanceDataCache(59, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_05-5325
{-2091501892, new GilesGameBalanceDataCache(58, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_03-6306
{1565455675, new GilesGameBalanceDataCache(52, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-2176
{181033992, new GilesGameBalanceDataCache(61, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_06-3659
{1661413485, new GilesGameBalanceDataCache(52, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_07-3991
{-1533912122, new GilesGameBalanceDataCache(62, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-3996
{2112157587, new GilesGameBalanceDataCache(62, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-6378
{-1303413118, new GilesGameBalanceDataCache(63, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_07-8259
{-1733388800, new GilesGameBalanceDataCache(54, ItemType.Gem, false, FollowerType.None)}, //Emerald_07-8261
{1612258797, new GilesGameBalanceDataCache(52, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-8260
{1682228650, new GilesGameBalanceDataCache(55, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-9071
{-1411866891, new GilesGameBalanceDataCache(54, ItemType.Gem, false, FollowerType.None)}, //Amethyst_07-2037
{-1512731045, new GilesGameBalanceDataCache(52, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-2899
{88667231, new GilesGameBalanceDataCache(60, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_04-3797
{-2115689177, new GilesGameBalanceDataCache(55, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_02-5676
{-1656023997, new GilesGameBalanceDataCache(59, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_04-11365
{-136814294, new GilesGameBalanceDataCache(62, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_04-11892
{329204073, new GilesGameBalanceDataCache(61, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_02-1331
{-270936736, new GilesGameBalanceDataCache(63, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_07-2509
{-1616888602, new GilesGameBalanceDataCache(63, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_06-3566
{-1656023999, new GilesGameBalanceDataCache(55, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_02-6299
{329204072, new GilesGameBalanceDataCache(56, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_01-6293
{-733829190, new GilesGameBalanceDataCache(58, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-7011
{1661414573, new GilesGameBalanceDataCache(62, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_06-8628
{181033994, new GilesGameBalanceDataCache(63, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_08-9102
{1603007816, new GilesGameBalanceDataCache(54, ItemType.Gem, false, FollowerType.None)}, //Ruby_07-11231
{-1962741148, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //HealthGlobe_02-15599
{-334278174, new GilesGameBalanceDataCache(60, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_04-11811
{1539238480, new GilesGameBalanceDataCache(60, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-13479
{-1656023996, new GilesGameBalanceDataCache(61, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_05-16080
{1815809036, new GilesGameBalanceDataCache(57, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_02-16556
{40857598, new GilesGameBalanceDataCache(61, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-18151
{329204074, new GilesGameBalanceDataCache(62, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_03-18904
{-1656023998, new GilesGameBalanceDataCache(57, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_03-19117
{329202986, new GilesGameBalanceDataCache(51, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_04-21596
{1755622722, new GilesGameBalanceDataCache(53, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-1940
{1236607150, new GilesGameBalanceDataCache(62, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_03-3679
{-231801345, new GilesGameBalanceDataCache(63, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_06-7909
{365492428, new GilesGameBalanceDataCache(58, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-8662
{620036244, new GilesGameBalanceDataCache(56, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-11733
{88667228, new GilesGameBalanceDataCache(54, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_01-19036
{-875942697, new GilesGameBalanceDataCache(61, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_04-21441
{-363389484, new GilesGameBalanceDataCache(63, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_08-21449
{-270936737, new GilesGameBalanceDataCache(62, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_08-3422
{1661414568, new GilesGameBalanceDataCache(54, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_01-4034
{2112156498, new GilesGameBalanceDataCache(53, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-4089
{-2091501890, new GilesGameBalanceDataCache(62, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_05-5750
{1539238481, new GilesGameBalanceDataCache(61, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-7305
{-363389489, new GilesGameBalanceDataCache(56, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_03-7443
{-1533912126, new GilesGameBalanceDataCache(55, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-7516
{-2091501893, new GilesGameBalanceDataCache(55, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_02-10478
{-231801349, new GilesGameBalanceDataCache(55, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_03-10866
{-231801348, new GilesGameBalanceDataCache(58, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_02-15614
{-1337761342, new GilesGameBalanceDataCache(52, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_01-15747
{620036245, new GilesGameBalanceDataCache(60, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-16062
{1755623809, new GilesGameBalanceDataCache(60, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-18920
{-242893288, new GilesGameBalanceDataCache(61, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-18926
{1612259884, new GilesGameBalanceDataCache(60, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-22557
{181033990, new GilesGameBalanceDataCache(56, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_04-22861
{1661414571, new GilesGameBalanceDataCache(60, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_04-2058
{1146967320, new GilesGameBalanceDataCache(51, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-5049
{-229899868, new GilesGameBalanceDataCache(60, ItemType.FollowerSpecial, false, FollowerType.Scoundrel)}, //JewelBox_Flippy-7661
{1146967321, new GilesGameBalanceDataCache(54, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-8882
{-242893290, new GilesGameBalanceDataCache(56, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-8875
{761439027, new GilesGameBalanceDataCache(60, ItemType.FollowerSpecial, false, FollowerType.Enchantress)}, //JewelBox_Flippy-12503
{-231801346, new GilesGameBalanceDataCache(62, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_04-18285
{-1656024000, new GilesGameBalanceDataCache(53, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_01-18617
{1755623808, new GilesGameBalanceDataCache(56, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-1483
{40857597, new GilesGameBalanceDataCache(60, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-8723
{-875943785, new GilesGameBalanceDataCache(52, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_05-11841
{1700549961, new GilesGameBalanceDataCache(55, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_01-14697
{-1656023994, new GilesGameBalanceDataCache(63, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_07-23400
{-2115690261, new GilesGameBalanceDataCache(51, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_07-440
{1771751032, new GilesGameBalanceDataCache(61, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_02-9926
{-270936742, new GilesGameBalanceDataCache(53, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_02-11499
{-2091501891, new GilesGameBalanceDataCache(61, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_04-15879
{1612259887, new GilesGameBalanceDataCache(63, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-16971
{88667229, new GilesGameBalanceDataCache(56, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_02-17893
{-242893287, new GilesGameBalanceDataCache(62, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-21766
{-635267401, new GilesGameBalanceDataCache(62, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_03-5833
{1661414574, new GilesGameBalanceDataCache(63, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_07-7199
{1771751034, new GilesGameBalanceDataCache(63, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_04-11249
{2112157584, new GilesGameBalanceDataCache(56, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-11552
{40857599, new GilesGameBalanceDataCache(62, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-14809
{-363389491, new GilesGameBalanceDataCache(52, ItemType.HandCrossbow, true, FollowerType.None)}, //handXbow_norm_base_flippy_01-15178
{-2115689172, new GilesGameBalanceDataCache(63, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_07-17487
{1755623810, new GilesGameBalanceDataCache(61, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-21671
{1539237393, new GilesGameBalanceDataCache(53, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-23717
{1236607147, new GilesGameBalanceDataCache(56, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_02-8621
{1905181656, new GilesGameBalanceDataCache(62, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_04-5332
{620035158, new GilesGameBalanceDataCache(53, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-490
{1288600121, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-8240
{1147341801, new GilesGameBalanceDataCache(57, ItemType.FollowerSpecial, false, FollowerType.Templar)}, //JewelBox_Flippy-6201
{-635267400, new GilesGameBalanceDataCache(63, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_04-24457
{1700549965, new GilesGameBalanceDataCache(63, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_05-7304
{-2115689178, new GilesGameBalanceDataCache(53, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_01-20183
{365492427, new GilesGameBalanceDataCache(55, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-1374
{290067593, new GilesGameBalanceDataCache(52, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_04-6474
{-136814296, new GilesGameBalanceDataCache(60, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_02-9438
{-1533913211, new GilesGameBalanceDataCache(52, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-15515
{-270936741, new GilesGameBalanceDataCache(55, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_05-23963
{-101310579, new GilesGameBalanceDataCache(55, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_01-3836
{88667233, new GilesGameBalanceDataCache(62, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_06-5434
{1905181655, new GilesGameBalanceDataCache(61, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_03-18857
{1236607151, new GilesGameBalanceDataCache(63, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_04-4638
{2112157585, new GilesGameBalanceDataCache(60, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-688
{88667234, new GilesGameBalanceDataCache(63, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_07-2315
{761439026, new GilesGameBalanceDataCache(57, ItemType.FollowerSpecial, false, FollowerType.Enchantress)}, //JewelBox_Flippy-5680
{-1337761338, new GilesGameBalanceDataCache(60, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_05-1977
{1236607146, new GilesGameBalanceDataCache(52, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_01-6741
{-635267402, new GilesGameBalanceDataCache(61, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_02-682
{1771751033, new GilesGameBalanceDataCache(62, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_03-1186
{290068682, new GilesGameBalanceDataCache(63, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_04-1455
{1815807951, new GilesGameBalanceDataCache(51, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_06-7216
{-242894376, new GilesGameBalanceDataCache(53, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-7932
{-1303413124, new GilesGameBalanceDataCache(53, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_01-7929
{-136814297, new GilesGameBalanceDataCache(56, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_01-10670
{-1303414207, new GilesGameBalanceDataCache(51, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_07-15282
{-270936740, new GilesGameBalanceDataCache(57, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_06-22060
{88666145, new GilesGameBalanceDataCache(52, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_07-25299
{-275669100, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-6906
{1815807952, new GilesGameBalanceDataCache(53, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_07-45607
{1905181654, new GilesGameBalanceDataCache(60, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_02-3074
{-231801350, new GilesGameBalanceDataCache(52, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_01-10063
{620036247, new GilesGameBalanceDataCache(62, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-17813
{-229899870, new GilesGameBalanceDataCache(54, ItemType.FollowerSpecial, false, FollowerType.Scoundrel)}, //JewelBox_Flippy-66005
{-270936743, new GilesGameBalanceDataCache(51, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_01-9813
{1771751031, new GilesGameBalanceDataCache(58, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_03-996
{-635267404, new GilesGameBalanceDataCache(56, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_02-20442
{-1616888607, new GilesGameBalanceDataCache(52, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_01-12628
{1147341800, new GilesGameBalanceDataCache(54, ItemType.FollowerSpecial, false, FollowerType.Templar)}, //JewelBox_Flippy-4753
{1539238482, new GilesGameBalanceDataCache(62, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-16011
{761439025, new GilesGameBalanceDataCache(54, ItemType.FollowerSpecial, false, FollowerType.Enchantress)}, //JewelBox_Flippy-17285
{1771751030, new GilesGameBalanceDataCache(55, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_02-19316
{1771751029, new GilesGameBalanceDataCache(52, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_01-10268
{1905180567, new GilesGameBalanceDataCache(53, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_04-10666
{181032905, new GilesGameBalanceDataCache(52, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_08-15728
{-246124382, new GilesGameBalanceDataCache(63, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-9313
{1809242064, new GilesGameBalanceDataCache(56, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-33597
{-1137443897, new GilesGameBalanceDataCache(61, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-10421
{1905181653, new GilesGameBalanceDataCache(56, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_01-18233
{329204075, new GilesGameBalanceDataCache(63, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_04-14928
{386201988, new GilesGameBalanceDataCache(62, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_07-59260
{-1849339001, new GilesGameBalanceDataCache(63, ItemType.Unknown, false, FollowerType.None)}, //Helm_norm_base_flippy-14437
{1700548876, new GilesGameBalanceDataCache(51, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_05-10421
{40856510, new GilesGameBalanceDataCache(53, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-9233
{-635267405, new GilesGameBalanceDataCache(52, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_01-10948
{-101311664, new GilesGameBalanceDataCache(52, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_05-2331
{255305004, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-12352
{402571149, new GilesGameBalanceDataCache(63, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_01-7789
{-247310303, new GilesGameBalanceDataCache(58, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-21253
{-327168932, new GilesGameBalanceDataCache(63, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-49247
{1810427985, new GilesGameBalanceDataCache(63, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-57737
{399013386, new GilesGameBalanceDataCache(60, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_01-16344
{-494657717, new GilesGameBalanceDataCache(63, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-19691
{-578170868, new GilesGameBalanceDataCache(61, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-17344
{-1953228509, new GilesGameBalanceDataCache(63, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-17168
{-1960344035, new GilesGameBalanceDataCache(58, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-18639
{253088131, new GilesGameBalanceDataCache(63, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-67142
{-1961529956, new GilesGameBalanceDataCache(62, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-26460
{1841261931, new GilesGameBalanceDataCache(61, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-24850
{-1855268606, new GilesGameBalanceDataCache(62, ItemType.Unknown, false, FollowerType.None)}, //Helm_norm_base_flippy-69134
{469402902, new GilesGameBalanceDataCache(60, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-54263
{-1271477944, new GilesGameBalanceDataCache(54, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-72607
{761573024, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger01-7009
{761573025, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger02-7561
{761573026, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger03-8774
{761573027, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger04-9277
{761573028, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger05-7156
{761573029, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger06-7390
{193631, new GilesGameBalanceDataCache(0, ItemType.Unknown, false, FollowerType.None)}, //A2C2AlcarnusPrisoner2-5586
{-1385743629, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //Lore_AzmodansOrders6-41895
{543691114, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-13741
{368302887, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-11107
{-636820188, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-18831
{-576445432, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-20698
{-275669102, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-1847
{435695962, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-10675
{-636820187, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-14144
{-115137849, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-31858
{1108898771, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-15883
{1134806015, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-17495
{-1689047028, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-12708
{-1051150314, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-18380
{623242820, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-5655
{-1660666895, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-18640
{-1661852814, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-36893
{368302886, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-52051
{-1205502140, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-52452
{-1162323497, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-43350
{-576445431, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-7553
{-807237754, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-22338
{-807237753, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-8329
{82340209, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-28639
{398631475, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-65157
{543691115, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-19549
{-1690232950, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-50444
{1717766203, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-12732
{844895417, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-1926
{1288600122, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-4913
{-1661852816, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-9603
{972140825, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-21742
{2129978459, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-30273
{844895416, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-68468
{-1690232948, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-31138
{255305005, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-6242
{364927530, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-7631
{623242821, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-11172
{-1690232949, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-64675
{1110084692, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-9210
{-638006108, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-59824
{368302888, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-11092
{82340210, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-29649
{521743063, new GilesGameBalanceDataCache(61, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Jeweler_Drop-7137
{-1171649812, new GilesGameBalanceDataCache(61, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Jeweler_Drop-60398
{872611723, new GilesGameBalanceDataCache(61, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Jeweler_Drop-3119
{2147165121, new GilesGameBalanceDataCache(62, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Jeweler_Drop-51842
{626121463, new GilesGameBalanceDataCache(63, ItemType.HandCrossbow, true, FollowerType.None)}, //handXbow_norm_unique_flippy_08-28023
{1738057815, new GilesGameBalanceDataCache(63, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_unique_flippy_02-46594
{-1864479819, new GilesGameBalanceDataCache(62, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_unique_flippy_04-52011
{1880318728, new GilesGameBalanceDataCache(62, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_unique_flippy_07-42689
{1845044989, new GilesGameBalanceDataCache(63, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_unique_flippy_08-45075
{1025903124, new GilesGameBalanceDataCache(61, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_unique_flippy_08-10113
{140743477, new GilesGameBalanceDataCache(60, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_unique_flippy_06-8301
{589357912, new GilesGameBalanceDataCache(60, ItemType.HandCrossbow, true, FollowerType.None)}, //handXbow_norm_unique_flippy_02-9863
{-1665099598, new GilesGameBalanceDataCache(57, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_unique_flippy_04-9022
{-1269640592, new GilesGameBalanceDataCache(60, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_unique_flippy_07-4037
{-1178676596, new GilesGameBalanceDataCache(60, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_unique_flippy_02-24734
{421779618, new GilesGameBalanceDataCache(63, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_unique_flippy_04-45679
{-1451977669, new GilesGameBalanceDataCache(61, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_unique_flippy_02-36957
{1034204571, new GilesGameBalanceDataCache(63, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_unique_flippy_06-26363
{1033018650, new GilesGameBalanceDataCache(56, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_unique_flippy_07-69093
{1949306026, new GilesGameBalanceDataCache(62, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_unique_flippy_04-35520
{-1241813500, new GilesGameBalanceDataCache(61, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_unique_flippy_09-12122
{-1264896908, new GilesGameBalanceDataCache(61, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_unique_flippy_05-15262
{-1450791748, new GilesGameBalanceDataCache(63, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_unique_flippy_06-41422
{-2078257915, new GilesGameBalanceDataCache(56, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_unique_flippy_02-1460
{-2041494364, new GilesGameBalanceDataCache(63, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_unique_flippy_05-46093
{1888620175, new GilesGameBalanceDataCache(60, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_unique_flippy_02-15158
{-1262525066, new GilesGameBalanceDataCache(63, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_unique_flippy_04-17468
{1664512741, new GilesGameBalanceDataCache(63, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_unique_flippy_06-26377
{1279577735, new GilesGameBalanceDataCache(63, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_unique_flippy_01-21404
{139557556, new GilesGameBalanceDataCache(63, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_unique_flippy_04-39065
        };
        #endregion




        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // *****                 Locate a new best target, update object caches etc.                *****
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************

        // Grab the list of world objects
        public static void RefreshDiaObjects()
        {
            // Blank current/last/next targets
            Vector3 vSafePointNear = targetCurrent != null ? targetCurrent.vThisPosition : vNullLocation;
            Vector3 vKitePointAvoid = vNullLocation;
            int iCurrentTargetRactorGUID = targetCurrent != null ? targetCurrent.iThisRActorGuid : -1;
            targetCurrent = null;

            // Reset all variables for target-weight finding
            bAnyTreasureGoblinsPresent = false;
            //intell -- iCurrentMaxKillRadius = Zeta.CommonBot.Settings.CharacterSettings.Instance.KillRadius;
            iCurrentMaxKillRadius = (float)(settings.iMonsterKillRange); //intell
            iCurrentMaxLootRadius = Zeta.CommonBot.Settings.CharacterSettings.Instance.LootRadius;
            bStayPutDuringAvoidance = false;

            // Set up the fake object for the target handler
            thisFakeObject = null;

            // Not allowed to kill monsters due to profile/routine/combat targeting settings - just set the kill range to a third
            if (!ProfileManager.CurrentProfile.KillMonsters || !CombatTargeting.Instance.AllowedToKillMonsters)
            {
                iCurrentMaxKillRadius /= 3;
            }
            // Always have a minimum kill radius, so we're never getting whacked without retaliating
            if (iCurrentMaxKillRadius < 10)
                iCurrentMaxKillRadius = 10;

            // Not allowed to loots due to profile/routine/loot targeting settings - just set range to a quarter
            if (!ProfileManager.CurrentProfile.PickupLoot || !LootTargeting.Instance.AllowedToLoot)
            {
                iCurrentMaxLootRadius /= 4;
            }

            // Counter for how many cycles we extend or reduce our attack/kill radius, and our loot radius, after a last kill
            if (iKeepKillRadiusExtendedFor > 0)
                iKeepKillRadiusExtendedFor--;
            if (iKeepLootRadiusExtendedFor > 0)
                iKeepLootRadiusExtendedFor--;

            // Refresh buffs (so we can check for wrath being up to ignore ice balls and anything else like that)
            GilesRefreshBuffs();

            // Clear forcing close-range priority on mobs after XX period of time
            if (bForceCloseRangeTarget && DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds > iMillisecondsForceCloseRange)
            {
                bForceCloseRangeTarget = false;
            }

            // Bunch of variables used throughout
            int iUnitsSurrounding = 0;
            hashMonsterObstacleCache = new HashSet<GilesObstacle>();
            hashAvoidanceObstacleCache = new HashSet<GilesObstacle>();
            hashNavigationObstacleCache = new HashSet<GilesObstacle>();
            bAnyChampionsPresent = false;
            bAnyMobsInCloseRange = false;
            iLastDistance = 0f;
            bTravellingAvoidance = false;

            // Every 15 seconds, clear the "blackspots" where avoidance failed, so we can re-check them
            if (DateTime.Now.Subtract(lastClearedAvoidanceBlackspots).TotalSeconds > 15)
            {
                lastClearedAvoidanceBlackspots = DateTime.Now;
                hashAvoidanceBlackspot = new HashSet<GilesObstacle>();
            }

            // Clear our very short-term destructible blacklist within 3 seconds of last attacking a destructible
            if (bNeedClearDestructibles && DateTime.Now.Subtract(lastDestroyedDestructible).TotalMilliseconds > 2500)
            {
                bNeedClearDestructibles = false;
                hashRGUIDDestructibleBlacklist = new HashSet<int>();
            }

            // Clear our very short-term ignore-monster blacklist (from not being able to raycast on them or already dead units)
            if (bNeedClearTemporaryBlacklist && DateTime.Now.Subtract(lastTemporaryBlacklist).TotalMilliseconds > 3000)
            {
                bNeedClearTemporaryBlacklist = false;
                hashRGUIDTemporaryBlacklist = new HashSet<int>();
            }

            // Clear certain cache dictionaries sequentially, spaced out over time, to force data updates
            if (DateTime.Now.Subtract(lastClearedCacheDictionary).TotalMilliseconds >= 4000)
            {
                lastClearedCacheDictionary = DateTime.Now;
                iLastClearedCacheDictionary++;
                if (iLastClearedCacheDictionary > 5)
                    iLastClearedCacheDictionary = 1;
                switch (iLastClearedCacheDictionary)
                {
                    case 1:
                        dictGilesVectorCache = new Dictionary<int, Vector3>();
                        dictGilesObjectTypeCache = new Dictionary<int, GilesObjectType>();
                        dictGilesActorSNOCache = new Dictionary<int, int>();
                        dictGilesACDGUIDCache = new Dictionary<int, int>();
                        dictGilesLastHealthCache = new Dictionary<int, double>();
                        dictGilesLastHealthChecked = new Dictionary<int, int>();
                        break;
                    case 2:
                        dictGilesMonsterAffixCache = new Dictionary<int, MonsterAffixes>();
                        dictGilesMaxHealthCache = new Dictionary<int, double>();
                        dictionaryStoredMonsterTypes = new Dictionary<int, MonsterType>();
                        dictionaryStoredMonsterSizes = new Dictionary<int, MonsterSize>();
                        dictGilesBurrowedCache = new Dictionary<int, bool>();
                        dictSummonedByID = new Dictionary<int, int>();
                        break;
                    case 3:
                    case 4:
                    case 5:
                        dictGilesGameBalanceIDCache = new Dictionary<int, int>();
                        dictGilesDynamicIDCache = new Dictionary<int, int>();
                        dictGilesQualityCache = new Dictionary<int, ItemQuality>();
                        dictGilesQualityRechecked = new Dictionary<int, bool>();
                        dictGilesPickupItem = new Dictionary<int, bool>();
                        break;
                }
            }

            // Reset the counters for player-owned things
            iPlayerOwnedMysticAlly = 0;
            iPlayerOwnedGargantuan = 0;
            iPlayerOwnedZombieDog = 0;
            iPlayerOwnedDHPets = 0;

            // Reset the counters for monsters at various ranges
            iElitesWithinRange = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 };
            iAnythingWithinRange = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 };
            bAnyBossesInRange = false;

            // Flag for if we should search for an avoidance spot or not
            bRequireAvoidance = false;

            // Highest weight found as we progress through, so we can pick the best target at the end (the one with the highest weight)
            double iHighestWeightFound = 0;

            // Here's the list we'll use to store each object
            List<GilesObject> listGilesObjectCache = new List<GilesObject>();

            HashSet<int> hashDoneThisRactor = new HashSet<int>();

            // Now pull up all the data and store anything we want to handle in the super special cache list
            // Also use many cache dictionaries to minimize DB<->D3 memory hits, and speed everything up a lot
            foreach (Actor thisactor in ZetaDia.Actors.RActorList)
            {
                bool bWantThis = false;

                // Convert the actor to a DiaObject
                DiaObject thisobj = (DiaObject)thisactor;

                // Ractor GUID
                tmp_iThisRActorGuid = thisobj.RActorGuid;

                // See if we've already checked this ractor, this loop
                if (hashDoneThisRactor.Contains(tmp_iThisRActorGuid))
                {
                    continue;
                }
                hashDoneThisRactor.Add(tmp_iThisRActorGuid);

                // Temporary ractor GUID ignoring, to prevent 2 interactions in a very short time which can cause stucks
                if (iIgnoreThisForLoops > 0 && iIgnoreThisRactorGUID == tmp_iThisRActorGuid)
                {
                    continue;
                }

                // Check our extremely short-term destructible-blacklist
                if (hashRGUIDDestructibleBlacklist.Contains(tmp_iThisRActorGuid))
                {
                    continue;
                }
                // Check our extremely short-term destructible-blacklist
                if (hashRGUIDTemporaryBlacklist.Contains(tmp_iThisRActorGuid))
                {
                    continue;
                }
                // See if it's on our temporary blacklist (from being stuck targeting it), as long as it's distance is not extremely close
                if (hashRGUIDTemporaryIgnoreBlacklist.Contains(tmp_iThisRActorGuid))
                {
                    continue;
                }
                // Or on our more permanent "per-game" blacklist
                if (hashRGUIDIgnoreBlacklist.Contains(tmp_iThisRActorGuid))
                {
                    continue;
                }

                // Get the Actor SNO, cached if possible
                if (!dictGilesActorSNOCache.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisActorSNO))
                {
                    try
                    {
                        tmp_iThisActorSNO = thisobj.ActorSNO;
                    }
                    catch (Exception ex)
                    {
                        Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting ActorSNO for an object.");
                        Logging.WriteDiagnostic(ex.ToString());
                        continue;
                    }
                    dictGilesActorSNOCache.Add(tmp_iThisRActorGuid, tmp_iThisActorSNO);
                }

                var tempCommonData = thisobj.CommonData;

                // Check for navigation obstacle hashlist
                bool bThisObstacle = hashSNONavigationObstacles.Contains(tmp_iThisActorSNO);

                // See if it's something we should always ignore like ravens etc.
                if (!bThisObstacle && hashActorSNOIgnoreBlacklist.Contains(tmp_iThisActorSNO))
                {
                    continue;
                }

                // Start this object as off as unknown type
                tmp_ThisGilesObjectType = GilesObjectType.Unknown;
                // Either get the cached Giles object type, or calculate it fresh
                if (!bThisObstacle && !dictGilesObjectTypeCache.TryGetValue(tmp_iThisRActorGuid, out tmp_ThisGilesObjectType))
                {
                    // See if it's an avoidance first from the SNO
                    if (hashAvoidanceSNOList.Contains(tmp_iThisActorSNO))
                    {
                        // If avoidance is disabled, ignore this avoidance stuff
                        if (!settings.bEnableAvoidance)
                        {
                            continue;
                        }
                        // Avoidance isn't disabled, so set this object type to avoidance
                        tmp_ThisGilesObjectType = GilesObjectType.Avoidance;
                    }
                    // It's not an avoidance, so let's calculate it's object type "properly"
                    else
                    {
                        // Calculate the object type of this object
                        if (thisobj is DiaUnit)
                        {
                            if (tempCommonData == null)
                                continue;
                            if (thisobj.ACDGuid != tempCommonData.ACDGuid)
                                continue;
                            tmp_ThisGilesObjectType = GilesObjectType.Unit;
                        }
                        else if (hashForceSNOToItemList.Contains(tmp_iThisActorSNO) || thisobj is DiaItem)
                        {
                            if (tempCommonData == null)
                                continue;
                            if (thisobj.ACDGuid != tempCommonData.ACDGuid)
                                continue;
                            tmp_ThisGilesObjectType = GilesObjectType.Item;
                        }
                        // 138989 is the SNO for health well/health pools. The hashlist is for interactable objects like levers, wheels etc.
                        else if (thisobj is GizmoShrine || tmp_iThisActorSNO == 138989)
                            tmp_ThisGilesObjectType = GilesObjectType.Shrine;
                        else if (thisobj is GizmoDestructibleLootContainer)
                            tmp_ThisGilesObjectType = GilesObjectType.Destructible;
                        else if (thisobj is GizmoDestructible)
                            tmp_ThisGilesObjectType = GilesObjectType.Barricade;
                        else if (thisobj is GizmoLootContainer)
                            tmp_ThisGilesObjectType = GilesObjectType.Container;
                        else if (hashSNOInteractWhitelist.Contains(tmp_iThisActorSNO))
                            tmp_ThisGilesObjectType = GilesObjectType.Interactable;
                    }
                    // Now cache the object type
                    dictGilesObjectTypeCache.Add(tmp_iThisRActorGuid, tmp_ThisGilesObjectType);
                }
                // Null commondata checking for new DB builds
                switch (tmp_ThisGilesObjectType)
                {
                    case GilesObjectType.Unit:
                    case GilesObjectType.Item:
                    case GilesObjectType.Gold:
                        if (tempCommonData == null)
                        {
                            continue;
                        }
                        break;
                }

                // We couldn't get a valid object type, so ignore it
                if (!bThisObstacle && tmp_ThisGilesObjectType == GilesObjectType.Unknown)
                {
                    continue;
                }

                // Get the ACDGUID, cached if possible, only for non-avoidance stuff
                if (!bThisObstacle && tmp_ThisGilesObjectType != GilesObjectType.Avoidance)
                {
                    if (!dictGilesACDGUIDCache.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisACDGUID))
                    {
                        try
                        {
                            tmp_iThisACDGUID = thisobj.ACDGuid;
                        }
                        catch (Exception ex)
                        {
                            Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting ACDGUID for an object [" + tmp_iThisActorSNO.ToString() + "]");
                            Logging.WriteDiagnostic(ex.ToString());
                            continue;
                        }
                        dictGilesACDGUIDCache.Add(tmp_iThisRActorGuid, tmp_iThisACDGUID);
                    }
                    // No ACDGUID, so shouldn't be anything we want to deal with
                    if (tmp_iThisACDGUID == -1)
                    {
                        continue;
                    }
                }
                else
                {
                    // Give AOE's -1 ACDGUID, since it's not needed for avoidance stuff
                    tmp_iThisACDGUID = -1;
                }

                // We will set weight up later in RefreshDiaObjects after we process all valid items
                tmp_dThisWeight = 0;

                // Try and get a cached position for anything that isn't avoidance or units (avoidance and units can move, sadly, so we risk DB mis-reads for those things!
                if (tmp_ThisGilesObjectType != GilesObjectType.Avoidance && tmp_ThisGilesObjectType != GilesObjectType.Unit)
                {
                    // Get the position, cached if possible
                    if (!dictGilesVectorCache.TryGetValue(tmp_iThisRActorGuid, out tmp_vThisPosition))
                    {
                        try
                        {
                            tmp_vThisPosition = thisobj.Position;
                        }
                        catch (Exception ex)
                        {
                            Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting position for a static object [" + tmp_iThisActorSNO.ToString() + "]");
                            Logging.WriteDiagnostic(ex.ToString());
                            continue;
                        }
                        // Now cache it
                        dictGilesVectorCache.Add(tmp_iThisRActorGuid, tmp_vThisPosition);
                    }
                }
                // Ok pull up live-position data for units/avoidance now...
                else
                {
                    try
                    {
                        tmp_vThisPosition = thisobj.Position;
                    }
                    catch (Exception ex)
                    {
                        Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting position for a unit or avoidance object [" + tmp_iThisActorSNO.ToString() + "]");
                        Logging.WriteDiagnostic(ex.ToString());
                        continue;
                    }
                }
                // Calculate distance, don't rely on DB's internal method as this may hit Diablo 3 memory again
                tmp_fCentreDistance = Vector3.Distance(playerStatus.vCurrentPosition, tmp_vThisPosition);
                // Set radius-distance to centre distance at first
                tmp_fRadiusDistance = tmp_fCentreDistance;

                // Now store the location etc. of this obstacle and continue
                if (bThisObstacle)
                {
                    hashNavigationObstacleCache.Add(new GilesObstacle(tmp_vThisPosition, dictSNONavigationSize[tmp_iThisActorSNO], tmp_iThisActorSNO));
                    continue;
                }

                // Ignore stuff which has a Z-height-difference too great, it's probably on a different level etc. - though not avoidance!
                if (tmp_ThisGilesObjectType != GilesObjectType.Avoidance)
                {
                    // Calculate the z-height difference between our current position, and this object's position
                    float fThisHeightDifference = Math.Abs(playerStatus.vCurrentPosition.Z - tmp_vThisPosition.Z);
                    switch (tmp_ThisGilesObjectType)
                    {
                        case GilesObjectType.Unit:
                        case GilesObjectType.Barricade:
                            // Ignore monsters (units) who's Z-height is 14 foot or more than our own z-height
                            if (fThisHeightDifference >= 14f)
                            {
                                continue;
                            }
                            break;
                        case GilesObjectType.Item:
                            // Items at 26+ z-height difference (we don't want to risk missing items so much)
                            if (fThisHeightDifference >= 26f)
                            {
                                continue;
                            }
                            break;
                        case GilesObjectType.Gold:
                        case GilesObjectType.Globe:
                            // Gold/Globes at 11+ z-height difference
                            if (fThisHeightDifference >= 11f)
                            {
                                continue;
                            }
                            break;
                        case GilesObjectType.Destructible:
                        case GilesObjectType.Shrine:
                        case GilesObjectType.Container:
                            // Destructibles, shrines and containers are the least important, so a z-height change of only 7 is enough to ignore (help avoid stucks at stairs etc.)
                            if (fThisHeightDifference >= 7f)
                            {
                                continue;
                            }
                            break;
                        case GilesObjectType.Interactable:
                            // Special interactable objects
                            if (fThisHeightDifference >= 9f)
                            {
                                continue;
                            }
                            break;
                    }
                }

                // This is "internalname" for items, and just a "generic" name for objects and units - cached if possible
                if (!dictGilesInternalNameCache.TryGetValue(tmp_iThisRActorGuid, out tmp_sThisInternalName))
                {
                    try
                    {
                        tmp_sThisInternalName = thisobj.Name;
                    }
                    catch (Exception ex)
                    {
                        Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting InternalName for an object [" + tmp_iThisActorSNO.ToString() + "]");
                        Logging.WriteDiagnostic(ex.ToString());
                        continue;
                    }
                    dictGilesInternalNameCache.Add(tmp_iThisRActorGuid, tmp_sThisInternalName);
                }

                // Try and grab the dynamic id and game balance id, if necessary and if possible
                if (tmp_ThisGilesObjectType == GilesObjectType.Item)
                {
                    // Get the Dynamic ID, cached if possible
                    if (!dictGilesDynamicIDCache.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisDynamicID))
                    {
                        try
                        {
                            tmp_iThisDynamicID = tempCommonData.DynamicId;
                        }
                        catch (Exception ex)
                        {
                            Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting DynamicID for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                            Logging.WriteDiagnostic(ex.ToString());
                            continue;
                        }
                        dictGilesDynamicIDCache.Add(tmp_iThisRActorGuid, tmp_iThisDynamicID);
                    }
                    // Get the Game Balance ID, cached if possible
                    if (!dictGilesGameBalanceIDCache.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisBalanceID))
                    {
                        try
                        {
                            tmp_iThisBalanceID = tempCommonData.GameBalanceId;
                        }
                        catch (Exception ex)
                        {
                            Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting GameBalanceID for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                            Logging.WriteDiagnostic(ex.ToString());
                            continue;
                        }
                        dictGilesGameBalanceIDCache.Add(tmp_iThisRActorGuid, tmp_iThisBalanceID);
                    }
                }
                else
                {
                    tmp_iThisDynamicID = -1;
                    tmp_iThisBalanceID = -1;
                }

                // Variables used by multiple parts of the switch below
                int iCurrentMinimumStackSize;
                double iPercentage;

                // Now do stuff specific to object types
                switch (tmp_ThisGilesObjectType)
                {
                    // ************************************************************
                    // *****             Handle Unit-type Objects             *****
                    // ************************************************************
                    case GilesObjectType.Unit:

                        // See if this is a boss
                        tmp_unit_bThisBoss = hashBossSNO.Contains(tmp_iThisActorSNO);

                        // Dictionary based caching of monster types based on the SNO codes
                        MonsterType monsterType;

                        // See if we need to refresh the monster type or not
                        bool bAddToDictionary = !dictionaryStoredMonsterTypes.TryGetValue(tmp_iThisActorSNO, out monsterType);
                        bool bRefreshMonsterType = bAddToDictionary;

                        // If it's a boss and it was an ally, keep refreshing until it's not an ally
                        // Because some bosses START as allied for cutscenes etc. until they become hostile
                        if (tmp_unit_bThisBoss && !bRefreshMonsterType)
                        {
                            switch (monsterType)
                            {
                                case MonsterType.Ally:
                                case MonsterType.Scenery:
                                case MonsterType.Helper:
                                case MonsterType.Team:
                                    bRefreshMonsterType = true;
                                    break;
                            }
                        }

                        // Now see if we do need to get new data for this boss or not
                        if (bRefreshMonsterType)
                        {
                            try
                            {
                                SNORecordMonster monsterInfo = tempCommonData.MonsterInfo;
                                if (monsterInfo != null)
                                {
                                    // Force Jondar as an undead, since Diablo 3 sticks him as a permanent ally
                                    if (tmp_iThisActorSNO == 86624)
                                    {
                                        monsterType = MonsterType.Undead;
                                    }
                                    else
                                    {
                                        monsterType = monsterInfo.MonsterType;
                                    }
                                    // Is this going to be a new dictionary entry, or updating one already existing?
                                    if (bAddToDictionary)
                                        dictionaryStoredMonsterTypes.Add(tmp_iThisActorSNO, monsterType);
                                    else
                                        dictionaryStoredMonsterTypes[tmp_iThisActorSNO] = monsterType;
                                }
                                else
                                {
                                    monsterType = MonsterType.Undead;
                                }
                            }
                            catch (Exception ex)
                            {
                                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting monsterinfo and monstertype for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                Logging.WriteDiagnostic(ex.ToString());
                                Logging.WriteDiagnostic("ActorTypeAttempt=");
                                Logging.WriteDiagnostic(thisobj.ActorType.ToString());
                                continue;
                            }
                        }

                        // Make sure it's a valid monster type
                        switch (monsterType)
                        {
                            case MonsterType.Ally:
                            case MonsterType.Scenery:
                            case MonsterType.Helper:
                            case MonsterType.Team:
                                continue;
                        }

                        // health calculations
                        double dThisMaxHealth;
                        // Get the max health of this unit, a cached version if available, if not cache it
                        if (!dictGilesMaxHealthCache.TryGetValue(tmp_iThisRActorGuid, out dThisMaxHealth))
                        {
                            try
                            {
                                dThisMaxHealth = tempCommonData.GetAttribute<float>(ActorAttributeType.HitpointsMax);
                            }
                            catch (Exception ex)
                            {
                                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting attribute max health for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                Logging.WriteDiagnostic(ex.ToString());
                                continue;
                            }
                            dictGilesMaxHealthCache.Add(tmp_iThisRActorGuid, dThisMaxHealth);
                        }

                        // Now try to get the current health - using temporary and intelligent caching
                        // Health calculations
                        int iLastCheckedHealth;
                        double dThisCurrentHealth;
                        bool bHasCachedHealth;
                        // See if we already have a cached value for health or not for this monster
                        if (dictGilesLastHealthChecked.TryGetValue(tmp_iThisRActorGuid, out iLastCheckedHealth))
                        {
                            bHasCachedHealth = true;
                            iLastCheckedHealth++;
                            if (iLastCheckedHealth > 6)
                                iLastCheckedHealth = 1;
                            if (iCurrentTargetRactorGUID == tmp_iThisRActorGuid && iLastCheckedHealth > 3)
                                iLastCheckedHealth = 1;
                        }
                        else
                        {
                            bHasCachedHealth = false;
                            iLastCheckedHealth = 1;
                        }
                        // Update health once every 5 cycles, except for current target, which is every cycle
                        if (iLastCheckedHealth == 1)
                        {
                            try
                            {
                                dThisCurrentHealth = tempCommonData.GetAttribute<float>(ActorAttributeType.HitpointsCur);
                            }
                            catch
                            {
                                // This happens so frequently in DB/D3 that this fails, let's not even bother logging it anymore
                                //Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting current health for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                // Add this monster to our very short-term ignore list
                                hashRGUIDTemporaryBlacklist.Add(tmp_iThisRActorGuid);
                                lastTemporaryBlacklist = DateTime.Now;
                                bNeedClearTemporaryBlacklist = true;
                                continue;
                            }
                            if (!bHasCachedHealth)
                            {
                                dictGilesLastHealthCache.Add(tmp_iThisRActorGuid, dThisCurrentHealth);
                                dictGilesLastHealthChecked.Add(tmp_iThisRActorGuid, iLastCheckedHealth);
                            }
                            else
                            {
                                dictGilesLastHealthCache[tmp_iThisRActorGuid] = dThisCurrentHealth;
                                dictGilesLastHealthChecked[tmp_iThisRActorGuid] = iLastCheckedHealth;
                            }

                        }
                        else
                        {
                            dThisCurrentHealth = dictGilesLastHealthCache[tmp_iThisRActorGuid];
                            dictGilesLastHealthChecked[tmp_iThisRActorGuid] = iLastCheckedHealth;
                        }

                        // And finally put the two together for a current health percentage
                        tmp_unit_iThisHitPoints = dThisCurrentHealth / dThisMaxHealth;
                        // Unit is already dead
                        if (tmp_unit_iThisHitPoints <= 0d)
                        {
                            // Add this monster to our very short-term ignore list
                            hashRGUIDTemporaryBlacklist.Add(tmp_iThisRActorGuid);
                            lastTemporaryBlacklist = DateTime.Now;
                            bNeedClearTemporaryBlacklist = true;
                            continue;
                        }

                        // Only set treasure goblins to true *IF* they haven't disabled goblins! Then check the SNO in the goblin hash list!
                        tmp_unit_bThisTreasureGoblin = false;
                        // Flag this as a treasure goblin *OR* ignore this object altogether if treasure goblins are set to ignore
                        if (hashActorSNOGoblins.Contains(tmp_iThisActorSNO))
                        {
                            if (settings.iTreasureGoblinPriority != 0)
                            {
                                tmp_unit_bThisTreasureGoblin = true;
                            }
                            else
                            {
                                continue;
                            }
                        }

                        // Pull up the Monster Affix cached data
                        MonsterAffixes theseaffixes;
                        if (!dictGilesMonsterAffixCache.TryGetValue(tmp_iThisRActorGuid, out theseaffixes))
                        {
                            try
                            {
                                theseaffixes = tempCommonData.MonsterAffixes;
                                dictGilesMonsterAffixCache.Add(tmp_iThisRActorGuid, theseaffixes);
                            }
                            catch
                            {
                                theseaffixes = MonsterAffixes.None;
                            }
                        }

                        tmp_unit_bThisElite = theseaffixes.HasFlag(MonsterAffixes.Elite);
                        tmp_unit_bThisRare = theseaffixes.HasFlag(MonsterAffixes.Rare);
                        tmp_unit_bThisUnique = theseaffixes.HasFlag(MonsterAffixes.Unique);
                        tmp_unit_bThisMinion = theseaffixes.HasFlag(MonsterAffixes.Minion);

                        // Cancel altogether if it's not even in range, unless it's a boss or an injured treasure goblin
                        double dUseKillRadius = iCurrentMaxKillRadius;

                        // Bosses get extra radius
                        if (tmp_unit_bThisBoss)
                        {
                            if (tmp_iThisActorSNO != 80509) // Kulle Exception
                                dUseKillRadius *= 1.5;
                            // And even more if they're already injured
                            if (tmp_unit_iThisHitPoints <= 0.98)
                                dUseKillRadius *= 4;
                            // And make sure we have a MINIMUM range for bosses - incase they are at screen edge etc.
                            if (dUseKillRadius <= 200)
                                if (tmp_iThisActorSNO != 80509) // Kulle Exception
                                    dUseKillRadius = 200;
                        }
                        // Special short-range list to ignore weakling mobs
                        if (hashActorSNOShortRangeOnly.Contains(tmp_iThisActorSNO))
                            //dUseKillRadius = 12;

                        // Prevent long-range mobs beign ignored while they may be pounding on us
                        if (dUseKillRadius <= 30 && hashActorSNORanged.Contains(tmp_iThisActorSNO))
                            dUseKillRadius = 30; //intell

                        //GoatMutant_Ranged_A_Unique_Uber-10955 ActorSNO:	255996 	(act 1)
                        //DuneDervish_B_Unique_Uber-14252 ActorSNO: 		256000	(act 2)
                        //morluSpellcaster_A_Unique_Uber-17451 ActorSNO:	256015	(act 3)
                        if (tmp_iThisActorSNO == 256015 || tmp_iThisActorSNO == 256000 || tmp_iThisActorSNO == 255996)
                            dUseKillRadius = 80;

                        // Injured treasure goblins get a huge extra radius - since they don't stay on the map long if injured, anyway!
                        if (tmp_unit_bThisTreasureGoblin && (tmp_fCentreDistance <= 60 || tmp_unit_iThisHitPoints <= 0.99))
                        {
                            tmp_bForceLeapAgainst = true;
                            if (settings.iTreasureGoblinPriority <= 2)
                                dUseKillRadius *= 2.5;
                            else
                                dUseKillRadius *= 4;
                            // Minimum distance of 60
                            if (dUseKillRadius <= 60) dUseKillRadius = 60;
                        }
                        // Elitey type mobs and things
                        else if ((tmp_unit_bThisElite || tmp_unit_bThisRare || tmp_unit_bThisUnique || tmp_unit_bThisMinion))
                        {
                            tmp_bForceLeapAgainst = true;
                            if (tmp_unit_iThisHitPoints <= 0.99)
                            {
                                dUseKillRadius *= 2;
                                if (dUseKillRadius <= 90) dUseKillRadius = 90;
                            }
                            else
                            {
                                if (dUseKillRadius <= 60) dUseKillRadius = 60;
                            }
                        }
                        // Safety for Giles own portal-back-to-town for full-backpack
                        else if (bGilesForcedVendoring)
                        {
                            if (dUseKillRadius <= 60) dUseKillRadius = 60; //intell
                        }

                        // Now ignore any unit not within our kill or extended kill radius
                        if (tmp_fCentreDistance > dUseKillRadius)
                        {
                            continue;
                        }

                        // Safe is-attackable detection
                        tmp_unit_bThisAttackable = true;
                        if (tmp_unit_bThisBoss || theseaffixes.HasFlag(MonsterAffixes.Shielding))
                        {
                            try
                            {
                                DiaUnit thisunit = thisobj as DiaUnit;
                                tmp_unit_bThisAttackable = !thisunit.IsInvulnerable;
                            }
                            catch (Exception ex)
                            {
                                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting is-invulnerable attribute for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                Logging.WriteDiagnostic(ex.ToString());
                                tmp_unit_bThisAttackable = true;
                            }
                        }
                        // Inactive units like trees, withermoths etc. still underground, or in the sky 
                        // K: Use official api for this
                        if (tmp_unit_iThisHitPoints >= 1f || tmp_unit_bThisBoss)
                        {

                            // Get the burrowing data for this unit
                            bool bBurrowed;
                            if (!dictGilesBurrowedCache.TryGetValue(tmp_iThisRActorGuid, out bBurrowed) || tmp_unit_bThisBoss)
                            {
                                try
                                {
                                    DiaUnit thisunit = thisobj as DiaUnit;
                                    bBurrowed = !thisunit.IsAttackable;
                                }
                                catch (Exception ex)
                                {
                                    Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting is-untargetable or is-burrowed attribute for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                    Logging.WriteDiagnostic(ex.ToString());
                                    continue;
                                }
                                // Only cache it if it's NOT burrowed (if it *IS* - then we need to keep re-checking until it comes out!)
                                if (!bBurrowed)
                                {
                                    // Don't cache for bosses, as we have to check for bosses popping in and out of the game during a complex fight
                                    if (!tmp_unit_bThisBoss)
                                        dictGilesBurrowedCache.Add(tmp_iThisRActorGuid, bBurrowed);
                                }
                                else
                                {
                                    // Unit is burrowed, so we need to ignore it until it isn't!
                                    continue;
                                }
                            }
                        } // Only if at full health, else don't bother checking each loop

                        // See if we already have this monster's size stored, if not get it and cache it
                        if (!dictionaryStoredMonsterSizes.TryGetValue(tmp_iThisActorSNO, out tmp_unit_ThisMonsterSize))
                        {
                            try
                            {
                                SNORecordMonster monsterInfo = thisobj.MonsterInfo;
                                if (monsterInfo != null)
                                {
                                    tmp_unit_ThisMonsterSize = monsterInfo.MonsterSize;
                                    dictionaryStoredMonsterSizes.Add(tmp_iThisActorSNO, tmp_unit_ThisMonsterSize);
                                }
                                else
                                {
                                    tmp_unit_ThisMonsterSize = MonsterSize.Unknown;
                                }
                            }
                            catch (Exception ex)
                            {
                                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting monstersize info for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                Logging.WriteDiagnostic(ex.ToString());
                                continue;
                            }
                        }

                        // Retrieve collision sphere radius, cached if possible
                        if (!dictGilesCollisionSphereCache.TryGetValue(tmp_iThisActorSNO, out tmp_fThisRadius))
                        {
                            try
                            {
                                tmp_fThisRadius = thisobj.CollisionSphere.Radius;
                                // Take 6 from the radius
                                tmp_fThisRadius -= 6f;
                                // Minimum range clamp
                                if (tmp_fThisRadius <= 1f)
                                    tmp_fThisRadius = 1f;
                                // Maximum range clamp
                                if (tmp_fThisRadius >= 20f)
                                    tmp_fThisRadius = 20f;
                            }
                            catch (Exception ex)
                            {
                                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting collisionsphere radius for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                Logging.WriteDiagnostic(ex.ToString());
                                continue;
                            }
                            dictGilesCollisionSphereCache.Add(tmp_iThisActorSNO, tmp_fThisRadius);
                        }

                        // A "fake distance" to account for the large-object size of monsters
                        tmp_fRadiusDistance -= (float)tmp_fThisRadius;
                        if (tmp_fRadiusDistance <= 1f)
                            tmp_fRadiusDistance = 1f;

                        // Handle bosses
                        if (tmp_unit_bThisBoss)
                        {
                            // Force to elite and add to the collision list
                            tmp_unit_bThisElite = true;
                            hashMonsterObstacleCache.Add(new GilesObstacle(tmp_vThisPosition, (tmp_fThisRadius * 0.15f), tmp_iThisActorSNO));
                        }
                        else
                        {
                            // Add to the collision-list
                            hashMonsterObstacleCache.Add(new GilesObstacle(tmp_vThisPosition, (tmp_fThisRadius * 0.10f), tmp_iThisActorSNO));
                        }
                        // All-in-one flag for quicker if checks throughout
                        tmp_bThisEliteRareUnique = (tmp_unit_bThisElite || tmp_unit_bThisRare || tmp_unit_bThisUnique || tmp_unit_bThisMinion);
                        // Special flags to decide whether to target anything at all
                        if (tmp_bThisEliteRareUnique || tmp_unit_bThisBoss)
                            bAnyChampionsPresent = true;

                        // Extended kill radius after last fighting, or when we want to force a town run
                        if ((settings.bExtendedKillRange && iKeepKillRadiusExtendedFor > 0) || bGilesForcedVendoring)
                        {
                            if (tmp_fCentreDistance <= dUseKillRadius)
                                bAnyMobsInCloseRange = true;
                        }
                        else
                        {
                            if (tmp_fCentreDistance <= settings.iMonsterKillRange)
                                bAnyMobsInCloseRange = true;
                        }
                        if (tmp_unit_bThisTreasureGoblin)
                            bAnyTreasureGoblinsPresent = true;

                        // Units with very high priority (1900+) allow an extra 50% on the non-elite kill slider range
                        if (!bAnyMobsInCloseRange && !bAnyChampionsPresent && !bAnyTreasureGoblinsPresent && tmp_fCentreDistance <= (settings.iMonsterKillRange * 1.5))
                        {
                            int iExtraPriority;
                            // Enable extended kill radius for specific unit-types
                            if (hashActorSNORanged.Contains(tmp_iThisActorSNO))
                            {
                                bAnyMobsInCloseRange = true;
                            }
                            if (!bAnyMobsInCloseRange && dictActorSNOPriority.TryGetValue(tmp_iThisActorSNO, out iExtraPriority))
                            {
                                if (iExtraPriority >= 1900)
                                {
                                    bAnyMobsInCloseRange = true;
                                }
                            }
                        }

                        // Store the dia unit reference (we'll be able to remove this if we update ractor list every single loop, yay!)
                        tmp_unit_diaUnit = null;
                        try
                        {
                            tmp_unit_diaUnit = (DiaUnit)thisobj;
                            // Prepare the fake object for target handler
                            if (thisFakeObject == null)
                                thisFakeObject = thisobj;
                        }
                        catch
                        {
                            continue;
                        }
                        bWantThis = true;
                        break;



                    // ************************************************************
                    // *****             Handle Item-type Objects             *****
                    // ************************************************************
                    case GilesObjectType.Item:

                        if (tmp_iThisBalanceID == -1)
                            continue;

                        // Try and pull up cached item data on this item, if not, add to our local memory cache
                        GilesGameBalanceDataCache tempGilesGameBalanceId;
                        if (!dictGilesGameBalanceDataCache.TryGetValue(tmp_iThisBalanceID, out tempGilesGameBalanceId))
                        {
                            DiaItem tempitem = thisobj as DiaItem;
                            if (tempitem != null)
                            {
                                try
                                {
                                    tmp_item_iThisLevel = tempitem.CommonData.Level;
                                    tmp_item_ThisDBItemType = tempitem.CommonData.ItemType;
                                    tmp_item_bThisOneHanded = tempitem.CommonData.IsOneHand;
                                    tmp_item_ThisFollowerType = tempitem.CommonData.FollowerSpecialType;
                                    dictGilesGameBalanceDataCache.Add(tmp_iThisBalanceID, new GilesGameBalanceDataCache(tmp_item_iThisLevel, tmp_item_ThisDBItemType, tmp_item_bThisOneHanded,
                                        tmp_item_ThisFollowerType));
                                    // Temporarily log stuff
                                    if (bLogBalanceDataForGiles)
                                    {
                                        FileStream LogStream = File.Open(sTrinityPluginPath + "_BalanceData_" + ZetaDia.Service.CurrentHero.BattleTagName + ".log", FileMode.Append, FileAccess.Write, FileShare.Read);
                                        using (StreamWriter LogWriter = new StreamWriter(LogStream))
                                        {
                                            LogWriter.WriteLine("{" + tmp_iThisBalanceID.ToString() + ", new GilesGameBalanceDataCache(" +
                                                tmp_item_iThisLevel.ToString() + ", ItemType." + tmp_item_ThisDBItemType.ToString() + ", " +
                                                tmp_item_bThisOneHanded.ToString().ToLower() + ", FollowerType." + tmp_item_ThisFollowerType.ToString() + ")}, //" + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                        }
                                        LogStream.Close();
                                    }
                                }
                                catch (Exception ex)
                                {
                                    Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting un-cached ACD Item data (level/item type etc.) for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                    Logging.WriteDiagnostic(ex.ToString());
                                    continue;
                                }
                            }
                            else
                            {
                                // Couldn't get the game balance data for this item, so ignore it for now
                                continue;
                            }
                        }
                        else
                        {
                            // We pulled this data from the dictionary cache, so use it instead of trying to get new data from DB/D3 memory!
                            tmp_item_iThisLevel = tempGilesGameBalanceId.iThisItemLevel;
                            tmp_item_ThisDBItemType = tempGilesGameBalanceId.thisItemType;
                            tmp_item_bThisOneHanded = tempGilesGameBalanceId.bThisOneHand;
                            tmp_item_ThisFollowerType = tempGilesGameBalanceId.thisFollowerType;
                        } // Able to get cached data or not?

                        // Error reading the item?
                        if (tmp_iThisBalanceID == -1)
                        {
                            continue;
                        }

                        // Calculate custom Giles item type
                        tmp_item_ThisGilesItemType = DetermineItemType(tmp_sThisInternalName, tmp_item_ThisDBItemType, tmp_item_ThisFollowerType);

                        // And temporarily store the base type
                        GilesBaseItemType tempbasetype = DetermineBaseType(tmp_item_ThisGilesItemType);
                        // Treat all globes as a yes
                        if (tmp_item_ThisGilesItemType == GilesItemType.HealthGlobe)
                        {
                            tmp_ThisGilesObjectType = GilesObjectType.Globe;

                            // Create or alter this cached object type
                            GilesObjectType tempobjecttype;
                            if (!dictGilesObjectTypeCache.TryGetValue(tmp_iThisRActorGuid, out tempobjecttype))
                                dictGilesObjectTypeCache.Add(tmp_iThisRActorGuid, tmp_ThisGilesObjectType);
                            else
                                dictGilesObjectTypeCache[tmp_iThisRActorGuid] = tmp_ThisGilesObjectType;

                            bWantThis = true;
                            break;
                        }
                        // Gold amount for gold piles
                        tmp_item_iThisGoldAmount = -1;

                        // Handle gold piles first
                        if (tmp_sThisInternalName.ToLower().StartsWith("gold"))
                        {
                            // Get the gold amount of this pile, cached if possible
                            if (!dictGilesGoldAmountCache.TryGetValue(tmp_iThisRActorGuid, out tmp_item_iThisGoldAmount))
                            {
                                try
                                {
                                    tmp_item_iThisGoldAmount = tempCommonData.GetAttribute<int>(ActorAttributeType.Gold);
                                }
                                catch
                                {
                                    Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting gold pile amount for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                    continue;
                                }
                                dictGilesGoldAmountCache.Add(tmp_iThisRActorGuid, tmp_item_iThisGoldAmount);
                            }

                            // Ignore gold piles that are (currently) too small...
                            iCurrentMinimumStackSize = settings.iMinimumGoldStack;
                            // Up to 40% less gold limit needed at close range
                            if (tmp_fCentreDistance <= 20f)
                            {
                                iPercentage = (1 - (tmp_fCentreDistance / 20)) * 0.4;
                                iCurrentMinimumStackSize -= (int)Math.Floor(iPercentage * iCurrentMinimumStackSize);
                            }
                            // And up to 40% or even higher extra gold limit at distant range
                            else if (tmp_fCentreDistance > 20f)
                            {
                                iPercentage = (tmp_fCentreDistance / 50) * 0.8;
                                iCurrentMinimumStackSize += (int)Math.Floor(iPercentage * iCurrentMinimumStackSize);
                            }
                            // Now check if this gold pile is currently less than this limit
                            if (tmp_item_iThisGoldAmount < iCurrentMinimumStackSize)
                            {
                                continue;
                            }
                            // Blacklist gold piles already in pickup radius range
                            if (tmp_fCentreDistance <= ZetaDia.Me.GoldPickUpRadius)
                            {
                                hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                                continue;
                            }
                            tmp_ThisGilesObjectType = GilesObjectType.Gold;

                            // Create or alter this cached object type
                            GilesObjectType tempobjecttype;
                            if (!dictGilesObjectTypeCache.TryGetValue(tmp_iThisRActorGuid, out tempobjecttype))
                                dictGilesObjectTypeCache.Add(tmp_iThisRActorGuid, tmp_ThisGilesObjectType);
                            else
                                dictGilesObjectTypeCache[tmp_iThisRActorGuid] = tmp_ThisGilesObjectType;

                            bWantThis = true;
                            break;
                        }

                        // Quality of item for "genuine" items
                        tmp_item_ThisQuality = ItemQuality.Invalid;
                        if (tempbasetype != GilesBaseItemType.Unknown && tempbasetype != GilesBaseItemType.HealthGlobe && tempbasetype != GilesBaseItemType.Gem && tempbasetype != GilesBaseItemType.Misc &&
                            !hashForceSNOToItemList.Contains(tmp_iThisActorSNO))
                        {
                            // Get the quality of this item, cached if possible
                            if (!dictGilesQualityCache.TryGetValue(tmp_iThisRActorGuid, out tmp_item_ThisQuality))
                            {
                                try
                                {
                                    tmp_item_ThisQuality = (ItemQuality)tempCommonData.GetAttribute<int>(ActorAttributeType.ItemQualityLevel);
                                }
                                catch
                                {
                                    Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting item-quality for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                    continue;
                                }
                                dictGilesQualityCache.Add(tmp_iThisRActorGuid, tmp_item_ThisQuality);
                                dictGilesQualityRechecked.Add(tmp_iThisRActorGuid, false);
                            }
                            else
                            {
                                // Because item-quality is such a sensitive thing, we don't want to risk losing items
                                // So we check a cached item quality a 2nd time - as long as it's the same, we won't check again
                                // However, if there's any inconsistencies, we keep checking, and keep the highest-read quality as the real value
                                if (!dictGilesQualityRechecked[tmp_iThisRActorGuid])
                                {
                                    ItemQuality temporaryItemQualityCheck = ItemQuality.Invalid;
                                    bool bFailedReading = false;
                                    try
                                    {
                                        temporaryItemQualityCheck = (ItemQuality)tempCommonData.GetAttribute<int>(ActorAttributeType.ItemQualityLevel);
                                    }
                                    catch
                                    {
                                        Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception double-checking item-quality for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                        bFailedReading = true;
                                    }
                                    // Make sure we didn't get a failure re-reading the quality, if we did we'll just keep re-checking until we don't
                                    if (!bFailedReading)
                                    {
                                        // If the newly-received quality is higher, then store the new quality
                                        if (temporaryItemQualityCheck > tmp_item_ThisQuality)
                                        {
                                            dictGilesQualityCache[tmp_iThisRActorGuid] = temporaryItemQualityCheck;
                                            tmp_item_ThisQuality = temporaryItemQualityCheck;
                                        }
                                        // And now flag it so we don't check this item again
                                        dictGilesQualityRechecked[tmp_iThisRActorGuid] = true;
                                    }
                                }
                            }
                        }

                        // Item stats
                        if (!_hashsetItemStatsLookedAt.Contains(tmp_iThisRActorGuid))
                        {
                            _hashsetItemStatsLookedAt.Add(tmp_iThisRActorGuid);
                            if (tempbasetype == GilesBaseItemType.Armor || tempbasetype == GilesBaseItemType.WeaponOneHand || tempbasetype == GilesBaseItemType.WeaponTwoHand ||
                                tempbasetype == GilesBaseItemType.WeaponRange || tempbasetype == GilesBaseItemType.Jewelry || tempbasetype == GilesBaseItemType.FollowerItem ||
                                tempbasetype == GilesBaseItemType.Offhand)
                            {
                                int iThisQuality;
                                ItemsDroppedStats.iTotal++;
                                if (tmp_item_ThisQuality >= ItemQuality.Legendary)
                                    iThisQuality = QUALITYORANGE;
                                else if (tmp_item_ThisQuality >= ItemQuality.Rare4)
                                    iThisQuality = QUALITYYELLOW;
                                else if (tmp_item_ThisQuality >= ItemQuality.Magic1)
                                    iThisQuality = QUALITYBLUE;
                                else
                                    iThisQuality = QUALITYWHITE;
                                ItemsDroppedStats.iTotalPerQuality[iThisQuality]++;
                                ItemsDroppedStats.iTotalPerLevel[tmp_item_iThisLevel]++;
                                ItemsDroppedStats.iTotalPerQPerL[iThisQuality, tmp_item_iThisLevel]++;
                            }
                            else if (tempbasetype == GilesBaseItemType.Gem)
                            {
                                int iThisGemType = 0;
                                ItemsDroppedStats.iTotalGems++;
                                if (tmp_item_ThisGilesItemType == GilesItemType.Topaz)
                                    iThisGemType = GEMTOPAZ;
                                if (tmp_item_ThisGilesItemType == GilesItemType.Ruby)
                                    iThisGemType = GEMRUBY;
                                if (tmp_item_ThisGilesItemType == GilesItemType.Emerald)
                                    iThisGemType = GEMEMERALD;
                                if (tmp_item_ThisGilesItemType == GilesItemType.Amethyst)
                                    iThisGemType = GEMAMETHYST;
                                ItemsDroppedStats.iGemsPerType[iThisGemType]++;
                                ItemsDroppedStats.iGemsPerLevel[tmp_item_iThisLevel]++;
                                ItemsDroppedStats.iGemsPerTPerL[iThisGemType, tmp_item_iThisLevel]++;
                            }
                            else if (tmp_item_ThisGilesItemType == GilesItemType.HealthPotion)
                            {
                                ItemsDroppedStats.iTotalPotions++;
                                ItemsDroppedStats.iPotionsPerLevel[tmp_item_iThisLevel]++;
                            }
                            else if (tmp_item_ThisGilesItemType == GilesItemType.InfernalKey)
                            {
                                ItemsDroppedStats.iTotalInfernalKeys++;
                            }
                            // See if we should update the stats file
                            if (DateTime.Now.Subtract(ItemStatsLastPostedReport).TotalSeconds > 10)
                            {
                                ItemStatsLastPostedReport = DateTime.Now;
                                OutputReport();
                            }
                        }

                        // Ignore it if it's not in range yet - allow legendary items to have 15 feet extra beyond our profile max loot radius
                        float fExtraRange = 0f;

                        if (iKeepLootRadiusExtendedFor > 0 || tmp_item_ThisQuality >= ItemQuality.Rare4)
                        {
                            fExtraRange = 30f;
                        }
                        if (iKeepLootRadiusExtendedFor > 0 || tmp_item_ThisQuality >= ItemQuality.Legendary)
                        {
                            fExtraRange = 50f;
                        }
                        if (tmp_fCentreDistance > (iCurrentMaxLootRadius + fExtraRange))
                        {
                            continue;
                        }

                        // Now see if we actually want it
                        if (settings.bUseGilesFilters)
                        {
                            // Get whether or not we want this item, cached if possible
                            bool bWantThisItem;
                            if (!dictGilesPickupItem.TryGetValue(tmp_iThisRActorGuid, out bWantThisItem))
                            {
                                bWantThisItem = GilesPickupItemValidation(tmp_sThisInternalName, tmp_item_iThisLevel, tmp_item_ThisQuality, tmp_iThisBalanceID, tmp_item_ThisDBItemType,
                                    tmp_item_ThisFollowerType, tmp_iThisDynamicID);
                                dictGilesPickupItem.Add(tmp_iThisRActorGuid, bWantThisItem);
                            }

                            // Using Giles filters
                            if (bWantThisItem)
                            {
                                // If we are trying to run a vendor-run, then don't deal with items atm
                                if (bGilesForcedVendoring)
                                {
                                    continue;
                                }
                                bWantThis = true;
                                break;
                            }
                        }
                        else
                        {
                            // Get whether or not we want this item, cached if possible
                            bool bWantThisItem;
                            if (!dictGilesPickupItem.TryGetValue(tmp_iThisRActorGuid, out bWantThisItem))
                            {
                                bWantThisItem = ItemManager.EvaluateItem((ACDItem)tempCommonData, ItemManager.RuleType.PickUp);
                                dictGilesPickupItem.Add(tmp_iThisRActorGuid, bWantThisItem);
                            }

                            // Using DB built-in item rules
                            if (bWantThisItem)
                            {
                                if (bGilesForcedVendoring)
                                {
                                    continue;
                                }
                                bWantThis = true;
                                break;
                            }
                        }
                        // Didn't pass giles pickup rules/DB internal rule match, so ignore it
                        continue;
                    // ************************************************************
                    // *****                   Handle Gold                    *****
                    // ************************************************************
                    // NOTE: Only identified as gold after *FIRST* loop as an "item" by above code
                    case GilesObjectType.Gold:
                        // Get the gold amount of this pile, cached if possible
                        if (!dictGilesGoldAmountCache.TryGetValue(tmp_iThisRActorGuid, out tmp_item_iThisGoldAmount))
                        {
                            try
                            {
                                tmp_item_iThisGoldAmount = tempCommonData.GetAttribute<int>(ActorAttributeType.Gold);
                            }
                            catch
                            {
                                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting gold pile amount for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                continue;
                            }
                            dictGilesGoldAmountCache.Add(tmp_iThisRActorGuid, tmp_item_iThisGoldAmount);
                        }

                        // Ignore gold piles that are (currently) too small...
                        iCurrentMinimumStackSize = settings.iMinimumGoldStack;
                        // Up to 40% less gold limit needed at close range
                        if (tmp_fCentreDistance <= 20f)
                        {
                            iPercentage = (1 - (tmp_fCentreDistance / 20)) * 0.4;
                            iCurrentMinimumStackSize -= (int)Math.Floor(iPercentage * iCurrentMinimumStackSize);
                        }
                        // And up to 40% or even higher extra gold limit at distant range
                        else if (tmp_fCentreDistance >= 30f)
                        {
                            iPercentage = (tmp_fCentreDistance / 40) * 0.4;
                            iCurrentMinimumStackSize += (int)Math.Floor(iPercentage * iCurrentMinimumStackSize);
                        }
                        // Now check if this gold pile is currently less than this limit
                        if (tmp_item_iThisGoldAmount < iCurrentMinimumStackSize)
                        {
                            continue;
                        }
                        // Blacklist gold piles already in pickup radius range
                        if (tmp_fCentreDistance <= ZetaDia.Me.GoldPickUpRadius)
                        {
                            hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                            continue;
                        }
                        tmp_ThisGilesObjectType = GilesObjectType.Gold;
                        bWantThis = true;
                        break;
                    // ************************************************************
                    // *****                  Handle Globes                   *****
                    // ************************************************************
                    // NOTE: Only identified as globe after *FIRST* loop as an "item" by above code
                    case GilesObjectType.Globe:
                        // Ignore it if it's not in range yet
                        if (tmp_fCentreDistance > iCurrentMaxLootRadius || tmp_fCentreDistance > 37f)
                        {
                            continue;
                        }
                        bWantThis = true;
                        break;
                    // ************************************************************
                    // *****              Handle Avoidance Objects            *****
                    // ************************************************************
                    case GilesObjectType.Avoidance:
                        // Note if you are looking here - an AOE object won't even appear at this stage if you have settings.bEnableAvoidance switched off!
                        if (!hashAvoidanceSNOList.Contains(tmp_iThisActorSNO))
                        {
                            Logging.WriteDiagnostic("GSDebug: Invalid avoidance detected, SNO=" + tmp_iThisActorSNO.ToString() + ", name=" + tmp_sThisInternalName + ", object type=" +
                                tmp_ThisGilesObjectType.ToString());
                            continue;
                        }
                        bool bIgnoreThisAvoidance = false;
                        double dThisHealthAvoid = dictAvoidanceHealth[tmp_iThisActorSNO];

                        // Monks with Serenity up ignore all AOE's
                        if (iMyCachedActorClass == ActorClass.Monk && hashPowerHotbarAbilities.Contains(SNOPower.Monk_Serenity) && GilesHasBuff(SNOPower.Monk_Serenity))
                        {
                            // Monks with serenity are immune
                            bIgnoreThisAvoidance = true;
                        }
                                             
                        
                        // Add it to the list of known avoidance objects, *IF* our health is lower than this avoidance health limit
                        if (!bIgnoreThisAvoidance && dThisHealthAvoid >= playerStatus.dCurrentHealthPct)
                        {
                            // Generate a "weight" for how badly we want to avoid this obstacle, based on a percentage of 100% the avoidance health is, multiplied into a max of 200 weight
                            double dThisWeight = (200 * dThisHealthAvoid);
                            hashAvoidanceObstacleCache.Add(new GilesObstacle(tmp_vThisPosition, (float)dictAvoidanceRadius[tmp_iThisActorSNO], tmp_iThisActorSNO, dThisWeight));
                            // Is this one under our feet? If so flag it up so we can find an avoidance spot
                            if (tmp_fCentreDistance <= dictAvoidanceRadius[tmp_iThisActorSNO])
                            {
                                bRequireAvoidance = true;
                                // Note if this is a travelling projectile or not so we can constantly update our safe points
                                if (hashAvoidanceSNOProjectiles.Contains(tmp_iThisActorSNO))
                                    bTravellingAvoidance = true;
                            }
                        }
                        // continue because we aren't actually treating this as a TARGET - avoidance has special handling after all targets are found
                        continue;

                    // ************************************************************
                    // *****             Handle Other-type Objects            *****
                    // ************************************************************
                    case GilesObjectType.Destructible:
                    case GilesObjectType.Barricade:
                    case GilesObjectType.Container:
                    case GilesObjectType.Shrine:
                    case GilesObjectType.Interactable:
                        // Check the primary object blacklist
                        if (hashSNOIgnoreBlacklist.Contains(tmp_iThisActorSNO))
                        {
                            continue;
                        }
                        // Ignore it if it's not in range yet
                        if (tmp_fCentreDistance > iCurrentMaxLootRadius || tmp_fCentreDistance > 50)
                        {
                            continue;
                        }
                        if (tmp_sThisInternalName.ToLower().StartsWith("minimapicon"))
                        {
                            // Minimap icons caused a few problems in the past, so this force-blacklists them
                            hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                            continue;
                        }
                        // Retrieve collision sphere radius, cached if possible
                        if (!dictGilesCollisionSphereCache.TryGetValue(tmp_iThisActorSNO, out tmp_fThisRadius))
                        {
                            try
                            {
                                tmp_fThisRadius = thisobj.CollisionSphere.Radius;
                                // Take 8 from the radius
                                tmp_fThisRadius -= 10f;
                                // Minimum range clamp
                                if (tmp_fThisRadius <= 1f)
                                    tmp_fThisRadius = 1f;
                                // Maximum range clamp
                                if (tmp_fThisRadius >= 16f)
                                    tmp_fThisRadius = 16f;
                            }
                            catch
                            {
                                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting collisionsphere radius for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                continue;
                            }
                            dictGilesCollisionSphereCache.Add(tmp_iThisActorSNO, tmp_fThisRadius);
                        }

                        // A "fake distance" to account for the large-object size of monsters
                        tmp_fRadiusDistance -= (float)tmp_fThisRadius;
                        if (tmp_fRadiusDistance <= 1f)
                            tmp_fRadiusDistance = 1f;

                        // Now for the specifics
                        int iThisPhysicsSNO;
                        int iExtendedRange;
                        double iMinDistance;
                        switch (tmp_ThisGilesObjectType)
                        {
                            case GilesObjectType.Interactable:
                                // Special interactables
                                if (tmp_fCentreDistance > 30f)
                                {
                                    continue;
                                }
                                tmp_fThisRadius = 4f;
                                bWantThis = true;
                                break;

                            case GilesObjectType.Shrine:
                                // Shrines
                                // Check if either we want to ignore all shrines
                                if (settings.bIgnoreAllShrines)
                                {
                                    // We're ignoring all shrines, so blacklist this one
                                    hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                                    continue;
                                }
                                // Already used, blacklist it and don't look at it again
                                bool bThisUsed = false;
                                try
                                {
                                    bThisUsed = (tempCommonData.GetAttribute<int>(ActorAttributeType.GizmoHasBeenOperated) > 0);
                                }
                                catch
                                {
                                    Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting shrine-been-operated attribute for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                    continue;
                                }
                                if (bThisUsed)
                                {
                                    // It's already open!
                                    hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                                    continue;
                                }
                                // Bag it!
                                tmp_fThisRadius = 4f;
                                bWantThis = true;
                                break;

                            case GilesObjectType.Barricade:
                                // Get the cached physics SNO of this object
                                if (!dictPhysicsSNO.TryGetValue(tmp_iThisActorSNO, out iThisPhysicsSNO))
                                {
                                    try
                                    {
                                        iThisPhysicsSNO = thisobj.PhysicsSNO;
                                    }
                                    catch
                                    {
                                        Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting physics SNO for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                        continue;
                                    }
                                    dictPhysicsSNO.Add(tmp_iThisActorSNO, iThisPhysicsSNO);
                                }
                                // No physics mesh? Ignore this destructible altogether
                                if (iThisPhysicsSNO <= 0)
                                {
                                    // No physics mesh on a destructible, probably bugged
                                    hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                                    continue;
                                }
                                // Set min distance to user-defined setting
                                iMinDistance = settings.iDestructibleAttackRange + (tmp_fThisRadius * 0.70);
                                if (bForceCloseRangeTarget)
                                    iMinDistance += 6f;
                                // Large objects, like logs - Give an extra xx feet of distance
                                if (dictSNOExtendedDestructRange.TryGetValue(tmp_iThisActorSNO, out iExtendedRange))
                                    iMinDistance = settings.iDestructibleAttackRange + iExtendedRange;
                                // This object isn't yet in our destructible desire range
                                if (iMinDistance <= 0 || tmp_fCentreDistance > iMinDistance)
                                {
                                    continue;
                                }
                                // Bag it!
                                bWantThis = true;
                                break;
                            case GilesObjectType.Destructible:
                                // Get the cached physics SNO of this object
                                if (!dictPhysicsSNO.TryGetValue(tmp_iThisActorSNO, out iThisPhysicsSNO))
                                {
                                    try
                                    {
                                        iThisPhysicsSNO = thisobj.PhysicsSNO;
                                    }
                                    catch
                                    {
                                        Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting physics SNO for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                        continue;
                                    }
                                    dictPhysicsSNO.Add(tmp_iThisActorSNO, iThisPhysicsSNO);
                                }
                                // No physics mesh? Ignore this destructible altogether
                                if (iThisPhysicsSNO <= 0)
                                {
                                    // No physics mesh on a destructible, probably bugged
                                    hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                                    continue;
                                }
                                // Set min distance to user-defined setting
                                iMinDistance = settings.iDestructibleAttackRange + (tmp_fThisRadius * 0.30);
                                if (bForceCloseRangeTarget)
                                    iMinDistance += 6f;
                                // Large objects, like logs - Give an extra xx feet of distance
                                if (dictSNOExtendedDestructRange.TryGetValue(tmp_iThisActorSNO, out iExtendedRange))
                                    iMinDistance = settings.iDestructibleAttackRange + iExtendedRange;
                                // This object isn't yet in our destructible desire range
                                if (iMinDistance <= 0 || tmp_fCentreDistance > iMinDistance)
                                {
                                    continue;
                                }
                                // Bag it!
                                bWantThis = true;
                                break;
                            case GilesObjectType.Container:
                                // We want to do some vendoring, so don't open anything new yet
                                if (bGilesForcedVendoring)
                                {
                                    continue;
                                }
                                // Already open, blacklist it and don't look at it again
                                bool bThisOpen = false;
                                try
                                {
                                    bThisOpen = (tempCommonData.GetAttribute<int>(ActorAttributeType.ChestOpen) > 0);
                                }
                                catch
                                {
                                    Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting container-been-opened attribute for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                    continue;
                                }
                                if (bThisOpen)
                                {
                                    // It's already open!
                                    hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                                    continue;
                                }
                                // Default to blacklisting all containers, then find reasons not to
                                bool bBlacklistThis = true;
                                iMinDistance = 0f;
                                // Get the cached physics SNO of this object
                                if (!dictPhysicsSNO.TryGetValue(tmp_iThisActorSNO, out iThisPhysicsSNO))
                                {
                                    try
                                    {
                                        iThisPhysicsSNO = thisobj.PhysicsSNO;
                                    }
                                    catch
                                    {
                                        Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting physics SNO for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                        continue;
                                    }
                                    dictPhysicsSNO.Add(tmp_iThisActorSNO, iThisPhysicsSNO);
                                }
                                // Any physics mesh? Give a minimum distance of 5 feet
                                if (tmp_sThisInternalName.ToLower().Contains("corpse") && settings.bIgnoreCorpses)
                                {
                                    bBlacklistThis = true;
                                }
                                else if (iThisPhysicsSNO > 0 || !settings.bIgnoreCorpses)
                                {
                                    Logging.WriteDiagnostic("[GilesTrinity] open container " + tmp_sThisInternalName + "[" + tmp_iThisActorSNO.ToString() + "]" + iThisPhysicsSNO);
                                    bBlacklistThis = false;
                                    iMinDistance = settings.iContainerOpenRange;
                                }
                                else
                                {
                                    bBlacklistThis = true;
                                }
                                // Whitelist for chests we want to open if we ever get close enough to them
                                if (hashSNOContainerWhitelist.Contains(tmp_iThisActorSNO))
                                {
                                    bBlacklistThis = false;
                                    if (settings.iContainerOpenRange > 0)
                                        iMinDistance = settings.iContainerOpenRange + 5;
                                }
                                else if (tmp_sThisInternalName.Contains("chest") && !tmp_sThisInternalName.Contains("chest_rare"))
                                {
                                    Logging.WriteDiagnostic("GSDebug: Possible Chest SNO: " + tmp_sThisInternalName + ", SNO=" + tmp_iThisActorSNO.ToString());
                                }
                                // Superlist for rare chests etc.
                                if (hashSNOContainerResplendant.Contains(tmp_iThisActorSNO))
                                {
                                    bBlacklistThis = false;
                                    if (settings.iContainerOpenRange > 0)
                                        iMinDistance = settings.iContainerOpenRange + 20;
                                    else
                                        iMinDistance = 10;
                                }
                                else if (tmp_sThisInternalName.Contains("chest_rare"))
                                {
                                    Logging.WriteDiagnostic("GSDebug: Possible Resplendant Chest SNO: " + tmp_sThisInternalName + ", SNO=" + tmp_iThisActorSNO.ToString());
                                }
                                // Blacklist this if it's something we should never bother looking at again
                                if (bBlacklistThis)
                                {
                                    hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid);
                                    continue;
                                }
                                if (iMinDistance <= 0 || tmp_fCentreDistance > iMinDistance)
                                {
                                    continue;
                                }
                                // Bag it!
                                tmp_fThisRadius = 4f;
                                bWantThis = true;
                                break;
                        } // Object switch on type (to seperate shrines, destructibles, barricades etc.)
                        break;
                } // Main huge switch on object type (core types - units, items, objects)
                // Nothing we want this loop!
                if (bWantThis)
                {
                    listGilesObjectCache.Add(new GilesObject(tmp_vThisPosition, tmp_ThisGilesObjectType, tmp_dThisWeight, tmp_fCentreDistance, tmp_fRadiusDistance, tmp_sThisInternalName,
                        tmp_iThisACDGUID, tmp_iThisRActorGuid, tmp_iThisDynamicID, tmp_iThisBalanceID, tmp_iThisActorSNO, tmp_item_iThisLevel, tmp_item_iThisGoldAmount, tmp_item_bThisOneHanded,
                        tmp_item_ThisQuality, tmp_item_ThisDBItemType, tmp_item_ThisFollowerType, tmp_item_ThisGilesItemType, tmp_unit_bThisElite, tmp_unit_bThisRare, tmp_unit_bThisUnique,
                        tmp_unit_bThisMinion, tmp_unit_bThisTreasureGoblin, tmp_unit_bThisBoss, tmp_unit_bThisAttackable, tmp_unit_iThisHitPoints, tmp_fThisRadius,
                        tmp_unit_ThisMonsterSize, tmp_unit_diaUnit, tmp_bThisEliteRareUnique, tmp_bForceLeapAgainst));
                }
            }

            // Reduce ignore-for-loops counter
            if (iIgnoreThisForLoops > 0)
                iIgnoreThisForLoops--;

            // If we have an avoidance under our feet, then create a new object which contains a safety point to move to
            // But only if we aren't force-cancelling avoidance for XX time
            bool bFoundSafeSpot = false;
            // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin!
            if (bRequireAvoidance && (!bAnyTreasureGoblinsPresent || settings.iTreasureGoblinPriority <= 2) &&
                DateTime.Now.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= iMillisecondsCancelledEmergencyMoveFor)
            {
                Vector3 vAnySafePoint = FindSafeZone(false, 1, vSafePointNear);
                // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach
                if (vAnySafePoint != vNullLocation)
                {
                    bFoundSafeSpot = true;
                    targetCurrent = new GilesObject(vAnySafePoint, GilesObjectType.Avoidance, 20000, Vector3.Distance(playerStatus.vCurrentPosition, vAnySafePoint), Vector3.Distance(playerStatus.vCurrentPosition, vAnySafePoint), "GilesSafePoint");
                }
                else
                {
                    // Didn't find any safe spot we could reach, so don't look for any more safe spots for at least 2.8 seconds
                    iMillisecondsCancelledEmergencyMoveFor = 2800;
                    timeCancelledEmergencyMove = DateTime.Now;
                }
            }





            // **********************************************************************************************
            // **********************************************************************************************
            // **********************************************************************************************
            // **********************************************************************************************
            // **********************************************************************************************
            // *****                             Give weights to objects                                *****
            // **********************************************************************************************
            // **********************************************************************************************
            // **********************************************************************************************
            // **********************************************************************************************
            // **********************************************************************************************


            bool bNeedToKite = false;
            bool bShouldTryKiting = false;
 
            // Now give each object a weight *IF* we aren't skipping direcly to a safe-spot
            if (!bFoundSafeSpot)
            {
                // Store if we are ignoring all units this cycle or not
                bool bIgnoreAllUnits = !bAnyChampionsPresent && !bAnyMobsInCloseRange && ((!bAnyTreasureGoblinsPresent && settings.iTreasureGoblinPriority >= 2) || settings.iTreasureGoblinPriority < 2) &&
                                playerStatus.dCurrentHealthPct >= 0.85d;
                bool bPrioritizeCloseRange = (bForceCloseRangeTarget || playerStatus.bIsRooted);
                
                foreach (GilesObject thisgilesobject in listGilesObjectCache)
                {
                    // Just to make sure each one starts at 0 weight...
                    thisgilesobject.dThisWeight = 0d;
                    // Now do different calculations based on the object type
                    switch (thisgilesobject.ThisGilesObjectType)
                    {
                        case GilesObjectType.Unit:
                            // ************************************************************
                            // *****                   Weight Units                   *****
                            // ************************************************************

                            // No champions, no mobs nearby, no treasure goblins to prioritize, and not injured, so skip mobs
                            if (bIgnoreAllUnits)
                            {
                                break;
                            }

                            // Total up monsters at various ranges
                            if (thisgilesobject.fRadiusDistance <= 50f)
                            {
                                bool bCountAsElite = (thisgilesobject.bThisEliteRareUnique || thisgilesobject.bThisBoss); //intell -- removed thisgilesobject.bThisTreasureGoblin
                                // Flag up any bosses in range
                                if (thisgilesobject.bThisBoss)
                                    bAnyBossesInRange = true;

                                if (thisgilesobject.fRadiusDistance <= 6f)
                                {
                                    iAnythingWithinRange[RANGE_6]++;
                                    if (bCountAsElite)
                                        iElitesWithinRange[RANGE_6]++;
                                }
                                if (thisgilesobject.fRadiusDistance <= 12f)
                                {
                                    iAnythingWithinRange[RANGE_12]++;
                                    if (bCountAsElite)
                                        iElitesWithinRange[RANGE_12]++;
                                }
                                if (thisgilesobject.fRadiusDistance <= 15f)
                                {
                                    iAnythingWithinRange[RANGE_15]++;
                                    if (bCountAsElite)
                                        iElitesWithinRange[RANGE_15]++;
                                }
                                if (thisgilesobject.fRadiusDistance <= 20f)
                                {
                                    iAnythingWithinRange[RANGE_20]++;
                                    if (bCountAsElite)
                                        iElitesWithinRange[RANGE_20]++;
                                }
                                if (thisgilesobject.fRadiusDistance <= 25f)
                                {
                                   
                                    iAnythingWithinRange[RANGE_25]++;
                                    if (bCountAsElite)
                                        iElitesWithinRange[RANGE_25]++;
                                }
                                if (thisgilesobject.fRadiusDistance <= 30f)
                                {
                                    iAnythingWithinRange[RANGE_30]++;
                                    if (bCountAsElite)
                                        iElitesWithinRange[RANGE_30]++;
                                }
                                if (thisgilesobject.fRadiusDistance <= 40f)
                                {
                                    iAnythingWithinRange[RANGE_40]++;
                                    if (bCountAsElite)
                                        iElitesWithinRange[RANGE_40]++;
                                }
                                if (thisgilesobject.fRadiusDistance <= 50f)
                                {
                                    iAnythingWithinRange[RANGE_50]++;
                                    if (bCountAsElite)
                                        iElitesWithinRange[RANGE_50]++;
                                }
                            }


                            // Force a close range target because we seem to be stuck *OR* if not ranged and currently rooted
                            if (bPrioritizeCloseRange)
                            {
                                thisgilesobject.dThisWeight = 20000 - (Math.Floor(thisgilesobject.fCentreDistance) * 200);
                                // Goblin priority KAMIKAZEEEEEEEE
                                if (thisgilesobject.bThisTreasureGoblin && settings.iTreasureGoblinPriority == 3)
                                    thisgilesobject.dThisWeight += 25000;
                            }

                            else
                            {
                                // Not attackable, could be shielded, make super low priority
                                if (!thisgilesobject.bThisAttackable)
                                {
                                    // Only 500 weight helps prevent it being prioritized over an unshielded
                                    thisgilesobject.dThisWeight = 500;
                                }
                                // Not forcing close-ranged targets from being stuck, so let's calculate a weight!
                                else
                                {
                                    // Starting weight of 5000 to beat a lot of crap weight stuff
                                    thisgilesobject.dThisWeight = 5000;

                                    // Distance as a percentage of max radius gives a value up to 1000 (1000 would be point-blank range)
                                    if (thisgilesobject.fRadiusDistance < iCurrentMaxKillRadius)
                                        thisgilesobject.dThisWeight += (1200 * (1 - (thisgilesobject.fRadiusDistance / iCurrentMaxKillRadius)));

                                    // Give extra weight to ranged enemies
                                    if ((thisgilesobject.unit_thisMonsterSize == MonsterSize.Ranged || hashActorSNORanged.Contains(tmp_iThisActorSNO)))
                                    {
                                        thisgilesobject.dThisWeight += 1100;
                                        thisgilesobject.bForceLeapAgainst = true;
                                    }

                                    // Give more weight to elites and minions
                                    //intell -- no weight for uber elites (key wardens), they already got 200 radius kill
                                    if ((thisgilesobject.bThisEliteRareUnique || thisgilesobject.bThisMinion) && tmp_iThisActorSNO != 256015 && tmp_iThisActorSNO != 256000 && tmp_iThisActorSNO != 255996)
                                        thisgilesobject.dThisWeight += 2000;

                                    // Give more weight to bosses
                                    if (thisgilesobject.bThisBoss)
                                        thisgilesobject.dThisWeight += 4000;

                                    // Swarmers/boss-likes get more weight
                                    if (thisgilesobject.unit_thisMonsterSize == MonsterSize.Swarm || thisgilesobject.unit_thisMonsterSize == MonsterSize.Boss)
                                        thisgilesobject.dThisWeight += 900;

                                    // Standard/big get a small bonus incase of "unknown" monster types being present
                                    if (thisgilesobject.unit_thisMonsterSize == MonsterSize.Standard || thisgilesobject.unit_thisMonsterSize == MonsterSize.Big)
                                        thisgilesobject.dThisWeight += 150;

                                    // Lower health gives higher weight - health is worth up to 300 extra weight
                                    if (thisgilesobject.iThisHitPoints < 0.20)
                                        thisgilesobject.dThisWeight += (300 * (1 - (thisgilesobject.iThisHitPoints / 0.5)));

                                    // Elites on low health get extra priority - up to 1500
                                    if ((thisgilesobject.bThisEliteRareUnique || thisgilesobject.bThisTreasureGoblin) && thisgilesobject.iThisHitPoints < 0.20)
                                        thisgilesobject.dThisWeight += (1500 * (1 - (thisgilesobject.iThisHitPoints / 0.45)));

                                    // Goblins on low health get extra priority - up to 2500
                                    if (settings.iTreasureGoblinPriority >= 2 && thisgilesobject.bThisTreasureGoblin && thisgilesobject.iThisHitPoints <= 0.98)
                                        thisgilesobject.dThisWeight += (3000 * (1 - (thisgilesobject.iThisHitPoints / 0.85)));

                                    // Bonuses to priority type monsters from the dictionary/hashlist set at the top of the code
                                    int iExtraPriority;
                                    if (dictActorSNOPriority.TryGetValue(thisgilesobject.iThisActorSNO, out iExtraPriority))
                                    {
                                        thisgilesobject.dThisWeight += iExtraPriority;
                                    }

                                    // Close range get higher weights the more of them there are, to prevent body-blocking
                                    // Plus a free bonus to anything close anyway
                                    if (thisgilesobject.fRadiusDistance <= 11f)
                                    {
                                        // Extra bonus for point-blank range
                                        iUnitsSurrounding++;
                                        // Give special "surrounded" weight to each unit
                                        thisgilesobject.dThisWeight += (200 * iUnitsSurrounding);
                                    }

                                    // Special additional weight for corrupt growths in act 4 ONLY if they are at close range (not a standard priority thing)
                                    if ((thisgilesobject.iThisActorSNO == 210120 || thisgilesobject.iThisActorSNO == 210268) && thisgilesobject.fCentreDistance <= 25f)
                                        thisgilesobject.dThisWeight += 2000;

                                    // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets
                                    if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f)
                                        thisgilesobject.dThisWeight += 400;

                                    // Lower the priority for EACH AOE *BETWEEN* us and the target, NOT counting the one directly under-foot, up to a maximum of 1500 reduction
                                    Vector3 point = thisgilesobject.vThisPosition;
                                    float fWeightRemoval = 0;
                                    foreach (GilesObstacle tempobstacle in hashAvoidanceObstacleCache.Where(cp =>
                                        GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point) &&
                                        cp.vThisLocation.Distance(point) > dictAvoidanceRadius[cp.iThisSNOID]))
                                    {
                                        fWeightRemoval += (float)tempobstacle.dThisWeight * 8;
                                    }
                                    if (fWeightRemoval > 1500)
                                        fWeightRemoval = 1500;
                                    thisgilesobject.dThisWeight -= fWeightRemoval;

                                    // Lower the priority if there is AOE *UNDER* the target, by the HIGHEST weight there only
                                    fWeightRemoval = 0;
                                    foreach (GilesObstacle tempobstacle in hashAvoidanceObstacleCache.Where(cp => cp.vThisLocation.Distance(point) <= dictAvoidanceRadius[cp.iThisSNOID] &&
                                        cp.vThisLocation.Distance(playerStatus.vCurrentPosition) <= (thisgilesobject.fRadiusDistance - 4f)))
                                    {
                                        // Up to 200 weight for a high-priority AOE - maximum 3400 weight reduction
                                        if (tempobstacle.dThisWeight > fWeightRemoval)
                                            fWeightRemoval = (float)tempobstacle.dThisWeight * 30;
                                    }
                                    thisgilesobject.dThisWeight -= fWeightRemoval;

                                    // Prevent going less than 300 yet to prevent annoyances (should only lose this much weight from priority reductions in priority list?)
                                    if (thisgilesobject.dThisWeight < 300)
                                        thisgilesobject.dThisWeight = 300;

                                    // Deal with treasure goblins - note, of priority is set to "0", then the is-a-goblin flag isn't even set for use here - the monster is ignored
                                    if (thisgilesobject.bThisTreasureGoblin)
                                    {
                                        // Logging goblin sightings
                                        if (lastGoblinTime == DateTime.Today)
                                        {
                                            iTotalNumberGoblins++;
                                            lastGoblinTime = DateTime.Now;
                                            Logging.Write("[GilesTrinity] Goblin #" + iTotalNumberGoblins.ToString() + " in sight. Distance=" + thisgilesobject.fCentreDistance);
                                        }
                                        else
                                        {
                                            if (DateTime.Now.Subtract(lastGoblinTime).TotalMilliseconds > 30000)
                                                lastGoblinTime = DateTime.Today;
                                        }
                                        // Original Trinity stuff for priority handling now
                                        switch (settings.iTreasureGoblinPriority)
                                        {
                                            case 1:
                                                // Treating goblins as "normal monsters". Ok so I lied a little in the config, they get a little extra weight really! ;)
                                                thisgilesobject.dThisWeight += 751;
                                                break;
                                            case 2:
                                                // Super-high priority option below... 
                                                thisgilesobject.dThisWeight += 10101;
                                                break;
                                            case 3:
                                                // KAMIKAZE SUICIDAL TREASURE GOBLIN RAPE AHOY!
                                                thisgilesobject.dThisWeight += 40000;
                                                break;
                                            // PS: 58008 is an awesome number on any calculator.

                                        }
                                    }
                                } // Forcing close range target or not?
                            } // This is an attackable unit
                            break;
                        case GilesObjectType.Item:
                        case GilesObjectType.Gold:
                            // ************************************************************
                            // *****                   Weight Items                   *****
                            // ************************************************************
                            // We'll weight them based on distance, giving gold less weight and close objects more
                            if (thisgilesobject.iThisGoldAmount > 0)
                                thisgilesobject.dThisWeight = 11000d - (Math.Floor(thisgilesobject.fCentreDistance) * 200d);
                            else
                                thisgilesobject.dThisWeight = 13000d - (Math.Floor(thisgilesobject.fCentreDistance) * 190d);
                            // Point-blank items get a weight increase 
                            if (thisgilesobject.iThisGoldAmount <= 0 && thisgilesobject.fCentreDistance <= 12f)
                                thisgilesobject.dThisWeight += 600d;
                            // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets
                            if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f)
                                thisgilesobject.dThisWeight += 600;
                            // Give yellows more weight
                            if (thisgilesobject.iThisGoldAmount <= 0 && thisgilesobject.ThisQuality >= ItemQuality.Rare4)
                                thisgilesobject.dThisWeight += 6000d;
                            // Give legendaries more weight
                            if (thisgilesobject.iThisGoldAmount <= 0 && thisgilesobject.ThisQuality >= ItemQuality.Legendary)
                                thisgilesobject.dThisWeight += 10000d;
                            // Are we prioritizing close-range stuff atm? If so limit it at a value 3k lower than monster close-range priority
                            if (bPrioritizeCloseRange)
                                thisgilesobject.dThisWeight = 18000 - (Math.Floor(thisgilesobject.fCentreDistance) * 200);
                            // If there's a monster in the path-line to the item, reduce the weight by 25%
                            if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                thisgilesobject.dThisWeight *= 0.75;
                            // See if there's any AOE avoidance in that spot or inbetween us, if so reduce the weight to 1
                            if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                thisgilesobject.dThisWeight = 1;
                            // Calculate a spot reaching a little bit further out from the item, to help pickup-movements
                            /*if (thisgilesobject.dThisWeight > 0)
                            {
                                if (thisgilesobject.iThisGoldAmount > 0)
                                    thisgilesobject.vThisPosition = MathEx.CalculatePointFrom(thisgilesobject.vThisPosition, playerStatus.vCurrentPosition, thisgilesobject.fCentreDistance + 2f);
                                else
                                    thisgilesobject.vThisPosition = MathEx.CalculatePointFrom(thisgilesobject.vThisPosition, playerStatus.vCurrentPosition, thisgilesobject.fCentreDistance + 1f);
                            }*/
                            break;
                        case GilesObjectType.Globe:
                            // ************************************************************
                            // *****               Weight Health Globes               *****
                            // ************************************************************
                            // Give all globes 0 weight (so never gone-to), unless we have low health, then go for them
                            if (playerStatus.dCurrentHealthPct > iEmergencyHealthGlobeLimit || !settings.bEnableGlobes)
                            {
                                thisgilesobject.dThisWeight = 0;
                            }
                            else
                            {
                                // Ok we have globes enabled, and our health is low...!
                                thisgilesobject.dThisWeight = 17000d - (Math.Floor(thisgilesobject.fCentreDistance) * 90d);
                                // Point-blank items get a weight increase
                                if (thisgilesobject.fCentreDistance <= 15f)
                                    thisgilesobject.dThisWeight += 3000d;
                                // Close items get a weight increase
                                if (thisgilesobject.fCentreDistance <= 60f)
                                    thisgilesobject.dThisWeight += 1500d;
                                // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets
                                if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f)
                                    thisgilesobject.dThisWeight += 400;
                                // Are we prioritizing close-range stuff atm? If so limit it at a value 3k lower than monster close-range priority
                                //if (bPrioritizeCloseRange)
                                //    thisgilesobject.dThisWeight = 22000 - (Math.Floor(thisgilesobject.fCentreDistance) * 200);
                                // If there's a monster in the path-line to the item, reduce the weight by 15% for each
                                Vector3 point = thisgilesobject.vThisPosition;
                                foreach (GilesObstacle tempobstacle in hashMonsterObstacleCache.Where(cp =>
                                    GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point)))
                                {
                                    thisgilesobject.dThisWeight *= 0.85;
                                }
                                // See if there's any AOE avoidance in that spot, if so reduce the weight by 10%
                                if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                    thisgilesobject.dThisWeight *= 0.9;
                                // Calculate a spot reaching a little bit further out from the globe, to help globe-movements
                                if (thisgilesobject.dThisWeight > 0)
                                    thisgilesobject.vThisPosition = MathEx.CalculatePointFrom(thisgilesobject.vThisPosition, playerStatus.vCurrentPosition, thisgilesobject.fCentreDistance + 3f);
                            }
                            break;
                        case GilesObjectType.Shrine:
                            // ************************************************************
                            // *****                  Weight Shrines                  *****
                            // ************************************************************
                            thisgilesobject.dThisWeight = 14500d - (Math.Floor(thisgilesobject.fCentreDistance) * 170d);
                            // Very close shrines get a weight increase
                            if (thisgilesobject.fCentreDistance <= 20f)
                                thisgilesobject.dThisWeight += 1000d;
                            switch (thisgilesobject.iThisActorSNO)
                            {
                                case 138989:
                                    // health pool
                                    if (playerStatus.dCurrentHealthPct >= .7)
                                        thisgilesobject.dThisWeight = 0;
                                    else
                                        thisgilesobject.dThisWeight += 4000d;
                                    break;
                                case 176074:
                                    // protection shrine
                                    break;
                                case 176076:
                                    // fortune shrine
                                    break;
                                case 176077:
                                    // frenzied shrine
                                    break;
                            }
                            if (thisgilesobject.dThisWeight > 0)
                            {
                                // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets
                                if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f)
                                    thisgilesobject.dThisWeight += 400;
                                // Are we prioritizing close-range stuff atm? If so limit it at a value 3k lower than monster close-range priority
                                if (bPrioritizeCloseRange)
                                    thisgilesobject.dThisWeight = 18500d - (Math.Floor(thisgilesobject.fCentreDistance) * 200);
                                // If there's a monster in the path-line to the item, reduce the weight by 25%
                                if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                    thisgilesobject.dThisWeight *= 0.75;
                                // See if there's any AOE avoidance in that spot, if so reduce the weight to 1
                                if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                    thisgilesobject.dThisWeight = 1;
                            }
                            break;
                        case GilesObjectType.Destructible:
                        case GilesObjectType.Barricade:
                            // ************************************************************
                            // *****               Weight Destructibles               *****
                            // ************************************************************
                            thisgilesobject.dThisWeight = 1750d - (Math.Floor(thisgilesobject.fCentreDistance) * 175d); //intell
                            // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets
                            if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f)
                                thisgilesobject.dThisWeight += 400;
                            // Close destructibles get a weight increase
                            if (thisgilesobject.fCentreDistance <= 16f)
                                thisgilesobject.dThisWeight += 1500d;
                            // If there's a monster in the path-line to the item, reduce the weight by 50%
                            if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                thisgilesobject.dThisWeight *= 0.5;
                            // See if there's any AOE avoidance in that spot, if so reduce the weight to 1
                            if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                thisgilesobject.dThisWeight = 1;
                            // Are we prioritizing close-range stuff atm? If so limit it at a value 3k lower than monster close-range priority
                            if (bPrioritizeCloseRange)
                                thisgilesobject.dThisWeight = 19200d - (Math.Floor(thisgilesobject.fCentreDistance) * 200d);
                            // Very close destructibles get a final weight increase
                            if (thisgilesobject.fCentreDistance <= 6f) //intell
                                thisgilesobject.dThisWeight += 2000d;
                            break;
                        case GilesObjectType.Interactable:
                            // ************************************************************
                            // *****          Weight Interactable Specials            *****
                            // ************************************************************
                            // Very close interactables get a weight increase
                            thisgilesobject.dThisWeight = 15000d - (Math.Floor(thisgilesobject.fCentreDistance) * 170d);
                            if (thisgilesobject.fCentreDistance <= 12f)
                                thisgilesobject.dThisWeight += 800d;
                            // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets
                            if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f)
                                thisgilesobject.dThisWeight += 400;
                            // If there's a monster in the path-line to the item, reduce the weight by 50%
                            if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                thisgilesobject.dThisWeight *= 0.5;
                            // See if there's any AOE avoidance in that spot, if so reduce the weight to 1
                            if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                thisgilesobject.dThisWeight = 1;
                            break;
                        case GilesObjectType.Container:
                            // ************************************************************
                            // *****                Weight Containers                 *****
                            // ************************************************************
                            // Very close containers get a weight increase
                            thisgilesobject.dThisWeight = 11000d - (Math.Floor(thisgilesobject.fCentreDistance) * 190d);
                            if (thisgilesobject.fCentreDistance <= 12f)
                                thisgilesobject.dThisWeight += 600d;
                            // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets
                            if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f)
                                thisgilesobject.dThisWeight += 400;
                            // If there's a monster in the path-line to the item, reduce the weight by 50%
                            if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                thisgilesobject.dThisWeight *= 0.5;
                            // See if there's any AOE avoidance in that spot, if so reduce the weight to 1
                            if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition)))
                                thisgilesobject.dThisWeight = 1;
                            break;
                    } // Switch on object type

                    // Force the character to stay where it is if there is nothing available that is out of avoidance stuff and we aren't already in avoidance stuff
                    if (thisgilesobject.dThisWeight == 1 && !bRequireAvoidance)
                    {
                        thisgilesobject.dThisWeight = 0;
                        bStayPutDuringAvoidance = true;
                    }

                    // Is the weight of this one higher than the current-highest weight? Then make this the new primary target!
                    if (thisgilesobject.dThisWeight > iHighestWeightFound && thisgilesobject.dThisWeight > 0)
                    {
                        // Clone the current Giles-cache object
                        targetCurrent = thisgilesobject.Clone();
                        iHighestWeightFound = thisgilesobject.dThisWeight;
                        // See if we can try attempting kiting later
                        bNeedToKite = false;
                        vKitePointAvoid = vNullLocation;
                        if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit)
                        {
                            Vector3 point = targetCurrent.vThisPosition;
                            if (hashAvoidanceObstacleCache.Any(cp => cp.vThisLocation.Distance(point) <= (dictAvoidanceRadius[cp.iThisSNOID] * 1.2) &&
                                cp.vThisLocation.Distance(playerStatus.vCurrentPosition) <= (thisgilesobject.fRadiusDistance - 4f)))
                            {
                                vKitePointAvoid = targetCurrent.vThisPosition;
                                bNeedToKite = true;
                            }
                        }
                    }

                } // Loop through all the objects and give them a weight

                // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin!
                if ((bShouldTryKiting || bNeedToKite) && (!bAnyTreasureGoblinsPresent || settings.iTreasureGoblinPriority <= 2) &&
                    DateTime.Now.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= iMillisecondsCancelledEmergencyMoveFor &&
                    (DateTime.Now.Subtract(timeCancelledKiteMove).TotalMilliseconds >= iMillisecondsCancelledKiteMoveFor ||
                    (DateTime.Now.Subtract(timeCancelledKiteMove).TotalMilliseconds >= 2500 && bNeedToKite)))
                {
                    Vector3 vAnySafePoint = FindSafeZone(false, 1, vKitePointAvoid, true);
                    // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach
                    if (vAnySafePoint != vNullLocation)
                    {
                        //Logging.Write("Found a kiting spot, attempting to kite!");
                        targetCurrent = new GilesObject(vAnySafePoint, GilesObjectType.Avoidance, 20000, Vector3.Distance(playerStatus.vCurrentPosition, vAnySafePoint), Vector3.Distance(playerStatus.vCurrentPosition, vAnySafePoint), "GilesKiting");
                        timeCancelledKiteMove = DateTime.Today;
                        iMillisecondsCancelledKiteMoveFor = 5000;
                    }
                    else
                    {
                        // Didn't find any kiting we could reach, so don't look for any more kite spots for at least 1.5 seconds
                        timeCancelledKiteMove = DateTime.Today;
                        iMillisecondsCancelledKiteMoveFor = 2500;
                    }
                }
            } // Not heading straight for a safe-spot?



            // No valid targets but we were told to stay put?
            if (targetCurrent == null && bStayPutDuringAvoidance && !bRequireAvoidance)
            {
                targetCurrent = new GilesObject(playerStatus.vCurrentPosition, GilesObjectType.Avoidance, 20000, 2f, 2f, "GilesStayPutPoint");
            }

            // Still no target, let's see if we should backtrack or wait for wrath to come off cooldown...
            if (targetCurrent == null)
            {
                // See if we should wait for [playersetting] milliseconds for possible loot drops before continuing run
                if (DateTime.Now.Subtract(lastHadUnitInSights).TotalMilliseconds <= settings.iKillLootDelay && DateTime.Now.Subtract(lastHadEliteUnitInSights).TotalMilliseconds <= 10000)
                {
                    targetCurrent = new GilesObject(playerStatus.vCurrentPosition, GilesObjectType.Avoidance, 20000, 2f, 2f, "GilesWaitForLootDrops");
                }

                // Now see if we need to do any backtracking
                if (targetCurrent == null && iTotalBacktracks >= 2 && settings.bEnableBacktracking && !playerStatus.bIsInTown) // Never bother with the 1st backtrack position nor if we are in town
                {
                    // See if we're already within 18 feet of our start position first
                    if (Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[1]) <= 18f)
                    {
                        vBacktrackList = new SortedList<int, Vector3>();
                        iTotalBacktracks = 0;
                    }
                    // See if we can raytrace to the final location and it's within 25 feet
                    if (iTotalBacktracks >= 2 && Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[1]) <= 25f &&
                        GilesCanRayCast(playerStatus.vCurrentPosition, vBacktrackList[1], NavCellFlags.AllowWalk))
                    {
                        vBacktrackList = new SortedList<int, Vector3>();
                        iTotalBacktracks = 0;
                    }
                    if (iTotalBacktracks >= 2)
                    {
                        // See if we can skip to the next backtracker location first
                        if (iTotalBacktracks >= 3)
                        {
                            if (Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks - 1]) <= 10f)
                            {
                                vBacktrackList.Remove(iTotalBacktracks);
                                iTotalBacktracks--;
                            }
                        }
                        targetCurrent = new GilesObject(vBacktrackList[iTotalBacktracks], GilesObjectType.Backtrack, 20000, Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks]), Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks]), "GilesBacktrack");
                    }
                }
                else
                {
                    vBacktrackList = new SortedList<int, Vector3>();
                    iTotalBacktracks = 0;
                } // End of backtracking check

               
            }

            // Still no target, let's end it all!
            if (targetCurrent == null)
            {
                return;
            }

            // Ok record the time we last saw any unit at all
            if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit)
            {
                lastHadUnitInSights = DateTime.Now;
                // And record when we last saw any form of elite
                if (targetCurrent.bThisBoss || targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin)
                    lastHadEliteUnitInSights = DateTime.Now;
            }

            // Record the last time our target changed etc.
            if (iCurrentTargetRactorGUID != targetCurrent.iThisRActorGuid)
            {
                dateSincePickedTarget = DateTime.Now;
                iTargetLastHealth = 0f;
            }
            else
            {
                // We're sticking to the same target, so update the target's health cache to check for stucks
                if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit)
                {
                    // Check if the health has changed, if so update the target-pick time before we blacklist them again
                    if (targetCurrent.iThisHitPoints != iTargetLastHealth)
                    {
                        dateSincePickedTarget = DateTime.Now;
                    }
                    // Now store the target's last-known health
                    iTargetLastHealth = targetCurrent.iThisHitPoints;
                }
            }
        } // Refresh object list from Diablo 3 memory RefreshDiaObjects()























        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // *****          Replace DB's actual core Behavior-Tree with Giles functions               *****
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************

        // **********************************************************************************************
        // *****           Blank decorator and action to wipe some of DB's behavior trees           *****
        // **********************************************************************************************

        private static void GilesTrinityStart(IBot bot)
        {
            if (!bPluginEnabled && bot != null)
            {
                Logging.Write("****************************************************");
                Logging.Write("WARNING: Giles Trinity is NOT YET ENABLED. Bot start detected");
                Logging.Write("****************************************************");
                Logging.Write("Ignore this message if you are not currently using Giles Trinity.");
                return;
            }

            // Recording of all the XML's in use this run
            try
            {
                string sThisProfile = Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile;
                if (sThisProfile != sLastProfileSeen)
                {
                    listProfilesLoaded.Add(sThisProfile);
                    sLastProfileSeen = sThisProfile;
                    if (sFirstProfileSeen == "")
                        sFirstProfileSeen = sThisProfile;
                }
            }
            catch
            {

            }
            // Update actors if possible (if already in-game)
            if (ZetaDia.IsInGame && !ZetaDia.IsLoadingWorld)
            {
                ZetaDia.Actors.Update();
            }
            bMappedPlayerAbilities = false;
            if (!bMaintainStatTracking)
            {
                ItemStatsWhenStartedBot = DateTime.Now;
                ItemStatsLastPostedReport = DateTime.Now;
                bMaintainStatTracking = true;
            }
            else
            {
                Log("Note: Maintaining item stats from previous run. To reset stats fully, please restart DB.");
            }
            foreach (var hook in TreeHooks.Instance.Hooks)
            {
                // Replace the combat behavior tree, as that happens first and so gets done quicker!
                if (hook.Key.Contains("Combat"))
                {
                    // Replace the pause just after identify stuff to ensure we wait before trying to run to vendor etc.
                    CanRunDecoratorDelegate canRunDelegateCombatTargetCheck = new CanRunDecoratorDelegate(GilesGlobalOverlord);
                    ActionDelegate actionDelegateCoreTarget = new ActionDelegate(GilesHandleTarget);
                    Sequence sequencecombat = new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegateCoreTarget)
                        );
                    hook.Value[0] = new Zeta.TreeSharp.Decorator(canRunDelegateCombatTargetCheck, sequencecombat);
                } // Vendor run hook
                // Wipe the vendorrun and loot behavior trees, since we no longer want them
                if (hook.Key.Contains("VendorRun"))
                {
                    Zeta.TreeSharp.Decorator GilesDecorator = hook.Value[0] as Zeta.TreeSharp.Decorator;
                    PrioritySelector GilesReplacement = GilesDecorator.Children[0] as PrioritySelector;

                    // Replace the pause just after identify stuff to ensure we wait before trying to run to vendor etc.
                    CanRunDecoratorDelegate canRunDelegateStashGilesPreStashPause = new CanRunDecoratorDelegate(GilesPreStashPauseOverlord);
                    ActionDelegate actionDelegatePrePause = new ActionDelegate(GilesStashPrePause);
                    ActionDelegate actionDelegatePause = new ActionDelegate(GilesStashPause);
                    Sequence sequencepause = new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegatePrePause),
                        new Zeta.TreeSharp.Action(actionDelegatePause)
                        );
                    GilesReplacement.Children[3] = new Zeta.TreeSharp.Decorator(canRunDelegateStashGilesPreStashPause, sequencepause);

                    // Replace DB stashing behavior tree with my optimized version with loot rule replacement
                    CanRunDecoratorDelegate canRunDelegateStashGilesOverlord = new CanRunDecoratorDelegate(GilesStashOverlord);
                    ActionDelegate actionDelegatePreStash = new ActionDelegate(GilesOptimisedPreStash);
                    ActionDelegate actionDelegateStashing = new ActionDelegate(GilesOptimisedStash);
                    ActionDelegate actionDelegatePostStash = new ActionDelegate(GilesOptimisedPostStash);
                    Sequence sequencestash = new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegatePreStash),
                        new Zeta.TreeSharp.Action(actionDelegateStashing),
                        new Zeta.TreeSharp.Action(actionDelegatePostStash),
                        new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegatePrePause),
                        new Zeta.TreeSharp.Action(actionDelegatePause)
                        )
                        );
                    GilesReplacement.Children[4] = new Zeta.TreeSharp.Decorator(canRunDelegateStashGilesOverlord, sequencestash);

                    // Replace DB vendoring behavior tree with my optimized & "one-at-a-time" version
                    CanRunDecoratorDelegate canRunDelegateSellGilesOverlord = new CanRunDecoratorDelegate(GilesSellOverlord);
                    ActionDelegate actionDelegatePreSell = new ActionDelegate(GilesOptimisedPreSell);
                    ActionDelegate actionDelegateSell = new ActionDelegate(GilesOptimisedSell);
                    ActionDelegate actionDelegatePostSell = new ActionDelegate(GilesOptimisedPostSell);
                    Sequence sequenceSell = new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegatePreSell),
                        new Zeta.TreeSharp.Action(actionDelegateSell),
                        new Zeta.TreeSharp.Action(actionDelegatePostSell),
                        new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegatePrePause),
                        new Zeta.TreeSharp.Action(actionDelegatePause)
                        )
                        );
                    GilesReplacement.Children[5] = new Zeta.TreeSharp.Decorator(canRunDelegateSellGilesOverlord, sequenceSell);

                    // Replace DB salvaging behavior tree with my optimized & "one-at-a-time" version
                    CanRunDecoratorDelegate canRunDelegateSalvageGilesOverlord = new CanRunDecoratorDelegate(GilesSalvageOverlord);
                    ActionDelegate actionDelegatePreSalvage = new ActionDelegate(GilesOptimisedPreSalvage);
                    ActionDelegate actionDelegateSalvage = new ActionDelegate(GilesOptimisedSalvage);
                    ActionDelegate actionDelegatePostSalvage = new ActionDelegate(GilesOptimisedPostSalvage);
                    Sequence sequenceSalvage = new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegatePreSalvage),
                        new Zeta.TreeSharp.Action(actionDelegateSalvage),
                        new Zeta.TreeSharp.Action(actionDelegatePostSalvage),
                        new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegatePrePause),
                        new Zeta.TreeSharp.Action(actionDelegatePause)
                        )
                        );
                    GilesReplacement.Children[6] = new Zeta.TreeSharp.Decorator(canRunDelegateSalvageGilesOverlord, sequenceSalvage);

                    CanRunDecoratorDelegate canRunDelegateGilesTownRunCheck = new CanRunDecoratorDelegate(GilesTownRunCheckOverlord);
                    hook.Value[0] = new Zeta.TreeSharp.Decorator(canRunDelegateGilesTownRunCheck, new PrioritySelector(GilesReplacement));
                } // Vendor run hook
                if (hook.Key.Contains("Loot"))
                {
                    // Replace the loot behavior tree with a blank one, as we no longer need it
                    CanRunDecoratorDelegate canRunDelegateBlank = new CanRunDecoratorDelegate(GilesBlankDecorator);
                    ActionDelegate actionDelegateBlank = new ActionDelegate(GilesBlankAction);
                    Sequence sequenceblank = new Sequence(
                        new Zeta.TreeSharp.Action(actionDelegateBlank)
                        );
                    hook.Value[0] = new Zeta.TreeSharp.Decorator(canRunDelegateBlank, sequenceblank);
                } // Vendor run hook
            }
        }

        // **********************************************************************************************
        // *****           Blank decorator and action to wipe some of DB's behavior trees           *****
        // **********************************************************************************************
        private static bool GilesBlankDecorator(object ret)
        {
            return false;
        }
        private static RunStatus GilesBlankAction(object ret)
        {
            return RunStatus.Success;
        }


        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // *****       Find fresh targets, start main BT if needed, cast any buffs needed etc.      *****
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************

        private static bool GilesGlobalOverlord(object ret)
        {
            // If we aren't in the game of a world is loading, don't do anything yet
            if (!ZetaDia.IsInGame || ZetaDia.IsLoadingWorld)
            {
                lastChangedZigZag = DateTime.Today;
                vPositionLastZigZagCheck = Vector3.Zero;
                return false;
            }

            // Big main-bot pause button
            if (bMainBotPaused)
            {
                return true;
            }

            // World ID safety caching incase it's ever unavailable
            if (ZetaDia.CurrentWorldDynamicId != -1)
                iCurrentWorldID = ZetaDia.CurrentWorldDynamicId;

            // Game difficulty, used really for vault on DH's
            if (ZetaDia.Service.CurrentHero.CurrentDifficulty != GameDifficulty.Invalid)
                iCurrentGameDifficulty = ZetaDia.Service.CurrentHero.CurrentDifficulty;

            // Refresh player buffs (to check for archon)
            GilesRefreshBuffs();

            // Store all of the player's abilities every now and then, to keep it cached and handy, also check for critical-mass timer changes etc.
            iCombatLoops++;
            if (!bMappedPlayerAbilities || iCombatLoops >= 50 || bRefreshHotbarAbilities)
            {
                // Update the cached player's cache
                ActorClass tempClass = ActorClass.Invalid;
                try
                {
                    tempClass = ZetaDia.Actors.Me.ActorClass;
                }
                catch
                {
                    Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception trying to get character class.");
                }
                if (tempClass != ActorClass.Invalid)
                    iMyCachedActorClass = ZetaDia.Actors.Me.ActorClass;
                iCombatLoops = 0;
                GilesRefreshHotbar(GilesHasBuff(SNOPower.Wizard_Archon));
                dictAbilityRepeatDelay = new Dictionary<SNOPower, int>(dictAbilityRepeatDefaults);
                if (settings.bEnableCriticalMass && (iMyCachedActorClass == ActorClass.Wizard || iMyCachedActorClass == ActorClass.WitchDoctor))
                {
                    dictAbilityRepeatDelay[SNOPower.Wizard_FrostNova] = 25;
                    dictAbilityRepeatDelay[SNOPower.Wizard_ExplosiveBlast] = 25;
                    dictAbilityRepeatDelay[SNOPower.Wizard_DiamondSkin] = 100;
                    dictAbilityRepeatDelay[SNOPower.Wizard_SlowTime] = 6000;
                    dictAbilityRepeatDelay[SNOPower.Wizard_WaveOfForce] = 1500;
                    dictAbilityRepeatDelay[SNOPower.Wizard_MirrorImage] = 1500;
                    dictAbilityRepeatDelay[SNOPower.Wizard_Archon_ArcaneBlast] = 1500;
                    dictAbilityRepeatDelay[SNOPower.Wizard_Teleport] = 2700;
                    dictAbilityRepeatDelay[SNOPower.Wizard_Archon_SlowTime] = 1500;
                    dictAbilityRepeatDelay[SNOPower.Wizard_Archon_Teleport] = 2700;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_SoulHarvest] = 1000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_SpiritWalk] = 1000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_Horrify] = 1000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_Gargantuan] = 20000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_SummonZombieDog] = 20000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_GraspOfTheDead] = 500;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_SpiritBarrage] = 2000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_Locust_Swarm] = 2000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_Haunt] = 2000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_Hex] = 3000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_MassConfusion] = 15000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_FetishArmy] = 20000;
                    dictAbilityRepeatDelay[SNOPower.Witchdoctor_BigBadVoodoo] = 20000;
                }
                if (settings.bWrath90Seconds && iMyCachedActorClass == ActorClass.Barbarian)
                {
                    dictAbilityRepeatDelay[SNOPower.Barbarian_Earthquake] = 90500;
                    dictAbilityRepeatDelay[SNOPower.Barbarian_CallOfTheAncients] = 90500;
                    dictAbilityRepeatDelay[SNOPower.Barbarian_WrathOfTheBerserker] = 90500;
                }
                // Pick an appropriate health set etc. based on class
                switch (iMyCachedActorClass)
                {
                    case ActorClass.Barbarian:
                        // What health % should we use a potion, or look for a globe
                        iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionBarb;
                        iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeBarb;
                        iKiteDistance = settings.iKiteDistanceBarb;
                        // The health percentage to avoid these objects at 
                        dictAvoidanceHealth = new Dictionary<int, double>(dictAvoidanceHealthBarb);
                        break;
                    case ActorClass.Monk:
                        // What health % should we use a potion, or look for a globe
                        iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionMonk;
                        iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeMonk;
                        iKiteDistance = 0;
                        // The health percentage to avoid these objects at 
                        dictAvoidanceHealth = new Dictionary<int, double>(dictAvoidanceHealthMonk);
                        break;
                    case ActorClass.Wizard:
                        // What health % should we use a potion, or look for a globe
                        iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionWiz;
                        iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeWiz;
                        iKiteDistance = settings.iKiteDistanceWiz;
                        // The health percentage to avoid these objects at 
                        dictAvoidanceHealth = new Dictionary<int, double>(dictAvoidanceHealthWizard);
                        break;
                    case ActorClass.WitchDoctor:
                        // What health % should we use a potion, or look for a globe
                        iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionWitch;
                        iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeWitch;
                        iKiteDistance = settings.iKiteDistanceWitch;
                        // The health percentage to avoid these objects at 
                        dictAvoidanceHealth = new Dictionary<int, double>(dictAvoidanceHealthWitch);
                        break;
                    case ActorClass.DemonHunter:
                        // What health % should we use a potion, or look for a globe
                        iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionDemon;
                        iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeDemon;
                        iKiteDistance = settings.iKiteDistanceDemon;
                        // The health percentage to avoid these objects at 
                        dictAvoidanceHealth = new Dictionary<int, double>(dictAvoidanceHealthDemon);
                        break;
                }
            }

            // Clear target current and reset key variables used during the target-handling function
            targetCurrent = null;
            bDontMoveMeIAmDoingShit = false;
            iTimesBlockedMoving = 0;
            bAlreadyMoving = false;
            lastMovementCommand = DateTime.Today;
            bAvoidDirectionBlacklisting = false;
            bWaitingForPower = false;
            bWaitingAfterPower = false;
            bWaitingForPotion = false;
            bWasRootedLastTick = false;

            // Let's calculate whether or not we want a new target list... update at least once every 150 milliseconds in main decorator
            bool bShouldRefreshDiaObjects = DateTime.Now.Subtract(lastRefreshedObjects).TotalMilliseconds >= 150;
            if (DateTime.Now.Subtract(TickInformation).TotalMinutes > 1)
            {
                Logging.WriteDiagnostic("[GilesTrinity] We still run smoothly");
                TickInformation = DateTime.Now;
            }

            // So, do we want a new target or not?
            if (bShouldRefreshDiaObjects)
            {
                // Update player data
                UpdateCachedPlayerData();

                // Check for death / player being dead
                if (playerStatus.dCurrentHealthPct <= 0)
                {
                    return false;
                }
                RefreshDiaObjects();
                // Update when we last refreshed with current time
                lastRefreshedObjects = DateTime.Now;
                // We have a target, start the target handler!
                if (targetCurrent != null)
                {
                    bWholeNewTarget = true;
                    bDontMoveMeIAmDoingShit = true;
                    bPickNewAbilities = true;
                    return true;
                }
            }
            else
            {
                // Return false here means we only do all of the below OOC stuff at max once every 150ms
                return false;
            }

            // Pop a potion when necessary
            if (playerStatus.dCurrentHealthPct <= iEmergencyHealthPotionLimit)
            {
                if (!playerStatus.bIsIncapacitated && GilesUseTimer(SNOPower.DrinkHealthPotion))
                {
                    ACDItem thisBestPotion = ZetaDia.Me.Inventory.Backpack.Where(i => i.IsPotion).OrderByDescending(p => p.HitpointsGranted).FirstOrDefault();
                    if (thisBestPotion != null)
                    {
                        WaitWhileAnimating(4, true);
                        ZetaDia.Me.Inventory.UseItem((thisBestPotion.DynamicId));
                    }
                    dictAbilityLastUse[SNOPower.DrinkHealthPotion] = DateTime.Now;
                    WaitWhileAnimating(3, true);
                }
            }

            // Clear the temporary blacklist every 90 seconds
            if (DateTime.Now.Subtract(dateSinceTemporaryBlacklistClear).TotalSeconds > 90)
            {
                dateSinceTemporaryBlacklistClear = DateTime.Now;
                hashRGUIDTemporaryIgnoreBlacklist = new HashSet<int>();
            }

            // Clear the full blacklist every 60 seconds
            if (DateTime.Now.Subtract(dateSinceBlacklistClear).TotalSeconds > 60)
            {
                dateSinceBlacklistClear = DateTime.Now;
                hashRGUIDIgnoreBlacklist = new HashSet<int>();
            }

            if (settings.bDebugInfo && bResetStatusText)
            {
                bResetStatusText = false;
                BotMain.StatusText = "[GilesTrinity] No more targets - DemonBuddy/profile management is now in control";
            }

            // Nothing to do... do we have some maintenance we can do instead, like out of combat buffing?
            lastChangedZigZag = DateTime.Today;
            vPositionLastZigZagCheck = Vector3.Zero;

            // Out of combat buffing etc. but only if we don't want to return to town etc.
            ACDAnimationInfo myAnimationState = ZetaDia.Me.CommonData.AnimationInfo;
            if (!playerStatus.bIsInTown && !bWantToTownRun && !bGilesForcedVendoring && myAnimationState != null && myAnimationState.State != AnimationState.Attacking && myAnimationState.State != AnimationState.Casting && myAnimationState.State != AnimationState.Channeling)
            {
                bDontSpamOutofCombat = false;
                powerBuff = GilesAbilitySelector(false, true, false);
                if (powerBuff.powerThis != SNOPower.None)
                {
                    WaitWhileAnimating(4, true);
                    ZetaDia.Me.UsePower(powerBuff.powerThis, powerBuff.vTargetLocation, powerBuff.iTargetWorldID, powerBuff.iTargetGUID);
                    powerLastSnoPowerUsed = powerBuff.powerThis;
                    dictAbilityLastUse[powerBuff.powerThis] = DateTime.Now;
                    WaitWhileAnimating(3, true);
                }
            }
            else
            {
                // Check if we are portalling to town, if so increase our kill radius temporarily
                if (myAnimationState != null)
                {
                    switch (myAnimationState.Current)
                    {
                        case SNOAnim.barbarian_male_HTH_Recall_Channel_01:
                        case SNOAnim.Barbarian_Female_HTH_Recall_Channel_01:
                        case SNOAnim.Monk_Male_recall_channel:
                        case SNOAnim.Monk_Female_recall_channel:
                        case SNOAnim.WitchDoctor_Male_recall_channel:
                        case SNOAnim.WitchDoctor_Female_recall_channel:
                        case SNOAnim.Wizard_Male_HTH_recall_channel:
                        case SNOAnim.Wizard_Female_HTH_recall_channel:
                        case SNOAnim.Demonhunter_Male_HTH_recall_channel:
                        case SNOAnim.Demonhunter_Female_HTH_recall_channel:
                            iKeepKillRadiusExtendedFor = 20;
                            break;
                    }
                }
            }


            // Ok let DemonBuddy do stuff this loop, since we're done for the moment
            return false;
        }


        // **********************************************************************************************
        // *****         Update the cached data on the player status - health, location etc.        *****
        // **********************************************************************************************

        private static void UpdateCachedPlayerData()
        {
            if (DateTime.Now.Subtract(playerStatus.lastUpdatedPlayer).TotalMilliseconds <= 50)
                return;

            // If we aren't in the game of a world is loading, don't do anything yet
            if (!ZetaDia.IsInGame || ZetaDia.IsLoadingWorld)
                return;

            var me = ZetaDia.Me;
            if (me == null)
                return;
            try
            {
                playerStatus.lastUpdatedPlayer = DateTime.Now;
                playerStatus.bIsInTown = me.IsInTown;
                playerStatus.bIsIncapacitated = (me.IsFeared || me.IsStunned || me.IsFrozen || me.IsBlind);
                playerStatus.bIsRooted = me.IsRooted;
                playerStatus.dCurrentHealthPct = me.HitpointsCurrentPct;
                playerStatus.dCurrentEnergy = me.CurrentPrimaryResource;
                playerStatus.dCurrentEnergyPct = playerStatus.dCurrentEnergy / me.MaxPrimaryResource;
                playerStatus.dDiscipline = me.CurrentSecondaryResource;
                playerStatus.dDisciplinePct = playerStatus.dDiscipline / me.MaxSecondaryResource;
                playerStatus.vCurrentPosition = me.Position;
                if (playerStatus.dCurrentEnergy >= iWaitingReservedAmount)
                    playerStatus.bWaitingForReserveEnergy = false;
                if (playerStatus.dCurrentEnergy < 20)
                    playerStatus.bWaitingForReserveEnergy = true;
                playerStatus.iMyDynamicID = me.CommonData.DynamicId;
                playerStatus.iMyLevel = me.Level;
            }
            catch
            {
                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception for grabbing player data.");
            }
        }

        // **********************************************************************************************
        // *****      Quick and Dirty routine just to force a wait until the character is "free"    *****
        // **********************************************************************************************
        public static void WaitWhileAnimating(int iMaxSafetyLoops = 10, bool bWaitForAttacking = false)
        {
            bool bKeepLooping = true;
            int iSafetyLoops = 0;
            while (bKeepLooping)
            {
                iSafetyLoops++;
                if (iSafetyLoops > iMaxSafetyLoops)
                    bKeepLooping = false;
                bool bIsAnimating = false;
                try
                {
                    ACDAnimationInfo myAnimationState = ZetaDia.Me.CommonData.AnimationInfo;
                    if (myAnimationState == null || myAnimationState.State == AnimationState.Casting || myAnimationState.State == AnimationState.Channeling)
                        bIsAnimating = true;
                    if (bWaitForAttacking && (myAnimationState == null || myAnimationState.State == AnimationState.Attacking))
                        bIsAnimating = true;
                }
                catch
                {
                    bIsAnimating = true;
                }
                if (!bIsAnimating)
                    bKeepLooping = false;
            }
        }


        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // *****          Start the process of moving to target object and dealing with it          *****
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************

        private static RunStatus GilesHandleTarget(object ret)
        {
            // If we aren't in the game of a world is loading, don't do anything yet
            if (!ZetaDia.IsInGame || ZetaDia.IsLoadingWorld)
            {
                return RunStatus.Success;
            }

            // Make sure we reset unstucker stuff here
            GilesPlayerMover.iTimesReachedStuckPoint = 0;
            GilesPlayerMover.vSafeMovementLocation = Vector3.Zero;
            GilesPlayerMover.timeLastRecordedPosition = DateTime.Now;

            // Big main-bot pause button
            if (bMainBotPaused)
            {
                return RunStatus.Success;
            }

            // Whether we should refresh the target list or not
            bool bShouldRefreshDiaObjects = false;

            // See if we should update hotbar abilities
            if (bRefreshHotbarAbilities)
                GilesRefreshHotbar(GilesHasBuff(SNOPower.Wizard_Archon));

            // Special pausing *AFTER* using certain powers
            if (bWaitingAfterPower && powerPrime.iForceWaitLoopsAfter >= 1)
            {
                if (powerPrime.iForceWaitLoopsAfter >= 1)
                    powerPrime.iForceWaitLoopsAfter--;
                if (powerPrime.iForceWaitLoopsAfter <= 0)
                    bWaitingAfterPower = false;
                return RunStatus.Running;
            }

            // Update player-data cache
            UpdateCachedPlayerData();
            // Check for death / player being dead
            if (playerStatus.dCurrentHealthPct <= 0)
            {
                return RunStatus.Success;
            }

            // See if we have been "newly rooted", to force target updates
            if (playerStatus.bIsRooted && !bWasRootedLastTick)
            {
                bWasRootedLastTick = true;
                bForceTargetUpdate = true;
            }
            if (!playerStatus.bIsRooted)
                bWasRootedLastTick = false;

            // Let's calculate whether or not we want a new target list...
            if (!bWholeNewTarget && !bWaitingForPower && !bWaitingForPotion)
            {
                // Update targets at least once every 80 milliseconds
                if (bForceTargetUpdate || bTravellingAvoidance ||
                    ((DateTime.Now.Subtract(lastRefreshedObjects).TotalMilliseconds >= 80 && targetCurrent.ThisGilesObjectType != GilesObjectType.Avoidance) ||
                      DateTime.Now.Subtract(lastRefreshedObjects).TotalMilliseconds >= 800))
                {
                    bShouldRefreshDiaObjects = true;
                }

                // If we AREN'T getting new targets - find out if we SHOULD because the current unit has died etc.
                if (!bShouldRefreshDiaObjects && targetCurrent.ThisGilesObjectType == GilesObjectType.Unit)
                {
                    if (targetCurrent.unit_thisDiaUnit == null || targetCurrent.unit_thisDiaUnit.BaseAddress == IntPtr.Zero)
                    {
                        bShouldRefreshDiaObjects = true;
                    }
                    else
                    {
                        // health calculations
                        double dThisMaxHealth;
                        // Get the max health of this unit, a cached version if available, if not cache it
                        if (!dictGilesMaxHealthCache.TryGetValue(tmp_iThisRActorGuid, out dThisMaxHealth))
                        {
                            try
                            {
                                dThisMaxHealth = targetCurrent.unit_thisDiaUnit.CommonData.GetAttribute<float>(ActorAttributeType.HitpointsMax);
                                dictGilesMaxHealthCache.Add(tmp_iThisRActorGuid, dThisMaxHealth);
                            }
                            catch
                            {
                                Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting attribute max health #2 for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]");
                                bShouldRefreshDiaObjects = true;
                            }
                        }
                        // Ok check we didn't fail getting the maximum health, now try to get live current health...
                        if (!bShouldRefreshDiaObjects)
                        {
                            try
                            {
                                double dTempHitpoints = (targetCurrent.unit_thisDiaUnit.CommonData.GetAttribute<float>(ActorAttributeType.HitpointsCur) / dThisMaxHealth);
                                if (dTempHitpoints <= 0d)
                                {
                                    bShouldRefreshDiaObjects = true;
                                }
                                else
                                {
                                    targetCurrent.iThisHitPoints = dTempHitpoints;
                                    targetCurrent.vThisPosition = targetCurrent.unit_thisDiaUnit.Position;
                                }
                            }
                            catch
                            {
                                bShouldRefreshDiaObjects = true;
                            }
                        }
                    }
                }
            }

            // So, after all that, do we actually want a new target list?
            if (!bWholeNewTarget && !bWaitingForPower && !bWaitingForPotion)
            {
                // If we *DO* want a new target list, do this... 
                if (bShouldRefreshDiaObjects)
                {
                    // Now call the function that refreshes targets
                    RefreshDiaObjects();
                    // Update when we last refreshed with current time
                    lastRefreshedObjects = DateTime.Now;

                    // No target, return success
                    if (targetCurrent == null)
                    {
                        return RunStatus.Success;
                    }
                    // Been trying to handle the same target for more than 30 seconds without damaging/reaching it? Blacklist it!
                    // Note: The time since target picked updates every time the current target loses health, if it's a monster-target
                    if (targetCurrent.ThisGilesObjectType != GilesObjectType.Avoidance &&
                        ((targetCurrent.ThisGilesObjectType != GilesObjectType.Unit && DateTime.Now.Subtract(dateSincePickedTarget).TotalSeconds > 12) ||
                         (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && !targetCurrent.bThisBoss && DateTime.Now.Subtract(dateSincePickedTarget).TotalSeconds > 40))
                        )
                    {
                        // NOTE: This only blacklists if it's remained the PRIMARY TARGET that we are trying to actually directly attack!
                        // So it won't blacklist a monster "on the edge of the screen" who isn't even being targetted
                        // Don't blacklist monsters on <= 50% health though, as they can't be in a stuck location... can they!? Maybe give them some extra time!
                        bool bBlacklistThis = true;
                        // PREVENT blacklisting a monster on less than 90% health unless we haven't damaged it for more than 25 seconds
                        if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit)
                        {
                            if (targetCurrent.bThisTreasureGoblin && settings.iTreasureGoblinPriority >= 3)
                                bBlacklistThis = false;
                            if (targetCurrent.iThisHitPoints <= 0.90 && DateTime.Now.Subtract(dateSincePickedTarget).TotalSeconds <= 25)
                                bBlacklistThis = false;
                        }
                        if (bBlacklistThis)
                        {
                            if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit)
                            {
                                Logging.Write("[GilesTrinity] Blacklisting a monster because of possible stuck issues. Monster=" + targetCurrent.sThisInternalName + " {" +
                                    targetCurrent.iThisActorSNO.ToString() + "}. Range=" + targetCurrent.fCentreDistance.ToString() + ", health %=" + targetCurrent.iThisHitPoints.ToString());
                            }
                            hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid);
                            dateSinceTemporaryBlacklistClear = DateTime.Now;
                        }
                    }
                    // Make sure we start trying to move again should we need to!
                    bAlreadyMoving = false;
                    lastMovementCommand = DateTime.Today;
                    bPickNewAbilities = true;
                }
                // Ok we didn't want a new target list, should we at least update the position of the current target, if it's a monster?
                else if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && targetCurrent.unit_thisDiaUnit != null && targetCurrent.unit_thisDiaUnit.BaseAddress != IntPtr.Zero)
                {
                    try
                    {
                        targetCurrent.vThisPosition = targetCurrent.unit_thisDiaUnit.Position;
                    }
                    catch
                    {
                        // Keep going anyway if we failed to get a new position from DemonBuddy
                        Logging.WriteDiagnostic("GSDEBUG: Caught position read failure in main target handler loop.");
                    }
                }
            }

            // This variable just prevents an instant 2-target update after coming here from the main decorator function above
            bWholeNewTarget = false;

            // Find a valid ability if the target is a monster

            if (bPickNewAbilities && !bWaitingForPower && !bWaitingForPotion)
            {
                bPickNewAbilities = false;
                if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit)
                {
                    // Pick a suitable ability
                    powerPrime = GilesAbilitySelector(false, false, false);
                    if (powerPrime.powerThis == SNOPower.None && !playerStatus.bIsIncapacitated)
                    {
                        iNoAbilitiesAvailableInARow++;
                        if (DateTime.Now.Subtract(lastRemindedAboutAbilities).TotalSeconds > 60 && iNoAbilitiesAvailableInARow >= 4)
                        {
                            lastRemindedAboutAbilities = DateTime.Now;
                            Logging.Write("Fatal Error: Couldn't find a valid attack ability. Not enough resource for any abilities or all on cooldown");
                            Logging.Write("If you get this message frequently, you should consider changing your build");
                            Logging.Write("Perhaps you don't have enough critical hit chance % for your current build, or just have a bad skill setup?");
                        }
                    }
                    else
                    {
                        iNoAbilitiesAvailableInARow = 0;
                    }
                }

                // Select an ability for destroying a destructible with in advance
                if (targetCurrent.ThisGilesObjectType == GilesObjectType.Destructible || targetCurrent.ThisGilesObjectType == GilesObjectType.Barricade)
                    powerPrime = GilesAbilitySelector(false, false, true);
            }

            // Pop a potion when necessary
            // Note that we force a single-loop pause first, to help potion popping "go off"
            if (playerStatus.dCurrentHealthPct <= iEmergencyHealthPotionLimit && !bWaitingForPower && !bWaitingForPotion && !playerStatus.bIsIncapacitated && GilesUseTimer(SNOPower.DrinkHealthPotion))
            {
                bWaitingForPotion = true;
                return RunStatus.Running;
            }
            if (bWaitingForPotion)
            {
                bWaitingForPotion = false;
                if (!playerStatus.bIsIncapacitated && GilesUseTimer(SNOPower.DrinkHealthPotion))
                {
                    ACDItem thisBestPotion = ZetaDia.Me.Inventory.Backpack.Where(i => i.IsPotion).OrderByDescending(p => p.HitpointsGranted).FirstOrDefault();
                    if (thisBestPotion != null)
                    {
                        WaitWhileAnimating(3, true);
                        ZetaDia.Me.Inventory.UseItem((thisBestPotion.DynamicId));
                    }
                    dictAbilityLastUse[SNOPower.DrinkHealthPotion] = DateTime.Now;
                    WaitWhileAnimating(2, true);
                }
            }

            // See if we can use any special buffs etc. while in avoidance
            if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance)
            {
                powerBuff = GilesAbilitySelector(true, false, false);
                if (powerBuff.powerThis != SNOPower.None)
                {
                    ZetaDia.Me.UsePower(powerBuff.powerThis, powerBuff.vTargetLocation, powerBuff.iTargetWorldID, powerBuff.iTargetGUID);
                    powerLastSnoPowerUsed = powerBuff.powerThis;
                    dictAbilityLastUse[powerBuff.powerThis] = DateTime.Now;
                }
            }

            // ************************************************************************
            // *****        Pick the destination point and range of target        *****
            // ************************************************************************

            // How close we need to get to the target before we consider it "reached"
            float fRangeRequired = 1f;
            float fDistanceReduction = 0f;

            // Set current destination to our current target's destination
            vCurrentDestination = targetCurrent.vThisPosition;
            switch (targetCurrent.ThisGilesObjectType)
            {
                // ***** Unit, we need to pick an ability to use and get within range *****
                case GilesObjectType.Unit:
                    // Treat the distance as closer based on the radius of monsters
                    fDistanceReduction = targetCurrent.fThisRadius;
                    if (bForceCloseRangeTarget)
                        fDistanceReduction -= 3f;
                    if (fDistanceReduction <= 0f)
                        fDistanceReduction = 0f;
                    // Pick a range to try to reach
                    fRangeRequired = powerPrime.powerThis == SNOPower.None ? 9f : powerPrime.iMinimumRange;
                    break;
                // ***** Item - need to get within 6 feet and then interact with it   *****
                case GilesObjectType.Item:
                    fRangeRequired = 5f;
                    // If we're having stuck issues, try forcing us to get closer to this item
                    if (bForceCloseRangeTarget)
                        fRangeRequired -= 1f;
                    // Try and randomize the distances required if we have problems looting this
                    if (iIgnoreThisRactorGUID == targetCurrent.iThisRActorGuid)
                    {
                        Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber));
                        fRangeRequired = (rndNum.Next(5)) + 2f;
                    }
                    // Treat the distance as closer if the X & Y distance are almost point-blank, for items
                    if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f)
                        fDistanceReduction += 1f;
                    break;
                // ***** Gold - need to get within pickup radius only         *****
                case GilesObjectType.Gold:
                    fRangeRequired = (float)ZetaDia.Me.GoldPickUpRadius;
                    if (fRangeRequired < 2f)
                        fRangeRequired = 2f;
                    break;
                // ***** Globes - need to get within pickup radius only         *****
                case GilesObjectType.Globe:
                    fRangeRequired = (float)ZetaDia.Me.GoldPickUpRadius;
                    if (fRangeRequired < 2f)
                        fRangeRequired = 2f;
                    if (fRangeRequired > 5f)
                        fRangeRequired = 5f;
                    break;
                // ***** Shrine & Container - need to get within 8 feet and interact  *****
                case GilesObjectType.Shrine:
                case GilesObjectType.Container:
                    // Treat the distance as closer based on the radius of the object
                    fDistanceReduction = targetCurrent.fThisRadius;
                    fRangeRequired = 3f;
                    if (bForceCloseRangeTarget)
                        fRangeRequired -= 2f;
                    // Treat the distance as closer if the X & Y distance are almost point-blank, for objects
                    if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f)
                        fDistanceReduction += 1f;
                    break;
                case GilesObjectType.Interactable:
                    // Treat the distance as closer based on the radius of the object
                    fDistanceReduction = targetCurrent.fThisRadius;
                    fRangeRequired = 12f;
                    if (bForceCloseRangeTarget)
                        fRangeRequired -= 2f;
                    // Check if it's in our interactable range dictionary or not
                    int iTempRange;
                    if (dictInteractableRange.TryGetValue(targetCurrent.iThisActorSNO, out iTempRange))
                    {
                        fRangeRequired = (float)iTempRange;
                    }
                    // Treat the distance as closer if the X & Y distance are almost point-blank, for objects
                    if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f)
                        fDistanceReduction += 1f;
                    break;
                // ***** Destructible - need to pick an ability and attack it         *****
                case GilesObjectType.Destructible:
                case GilesObjectType.Barricade:
                    // Pick a range to try to reach + (tmp_fThisRadius * 0.70);
                    fRangeRequired = powerPrime.powerThis == SNOPower.None ? 9f : powerPrime.iMinimumRange;
                    fDistanceReduction = targetCurrent.fThisRadius;
                    if (bForceCloseRangeTarget)
                        fDistanceReduction -= 3f;
                    if (fDistanceReduction <= 0f)
                        fDistanceReduction = 0f;
                    // Treat the distance as closer if the X & Y distance are almost point-blank, for destructibles
                    if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f)
                        fDistanceReduction += 1f;
                    break;
                // ***** Avoidance - need to pick an avoid location and move there    *****
                case GilesObjectType.Avoidance:
                    fRangeRequired = 2f;
                    // Treat the distance as closer if the X & Y distance are almost point-blank, for avoidance spots
                    if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f)
                        fDistanceReduction += 2f;
                    break;
                // ***** Backtrack Destination                                        *****
                case GilesObjectType.Backtrack:
                    fRangeRequired = 5f;
                    if (bForceCloseRangeTarget)
                        fRangeRequired -= 2f;
                    break;
            }

            // Maintain an area list of all zones we pass through/near while moving, for our custom navigation handler
            if (DateTime.Now.Subtract(lastAddedLocationCache).TotalMilliseconds >= 100)
            {
                lastAddedLocationCache = DateTime.Now;
                if (Vector3.Distance(playerStatus.vCurrentPosition, vLastRecordedLocationCache) >= 5f)
                {
                    hashSkipAheadAreaCache.Add(new GilesObstacle(playerStatus.vCurrentPosition, 20f, 0));
                }
            }

            // Maintain a backtrack list only while fighting monsters
            if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && settings.bEnableBacktracking &&
                (iTotalBacktracks == 0 || Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks]) >= 10f))
            {
                bool bAddThisBacktrack = true;
                // Check we aren't within 12 feet of 2 backtracks again (eg darting back & forth)
                if (iTotalBacktracks >= 2)
                {
                    if (Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks - 1]) < 12f)
                        bAddThisBacktrack = false;
                }
                if (bAddThisBacktrack)
                {
                    iTotalBacktracks++;
                    vBacktrackList.Add(iTotalBacktracks, playerStatus.vCurrentPosition);
                }
            }

            // Calculate the player's current distance from destination
            float fDistanceFromTarget = Vector3.Distance(playerStatus.vCurrentPosition, vCurrentDestination) - fDistanceReduction;
            if (fDistanceFromTarget < 0f)
                fDistanceFromTarget = 0f;

            // ************************************************************************
            // *****      Interact/use power on target if already in range        *****
            // ************************************************************************

            if (fRangeRequired <= 0f || fDistanceFromTarget <= fRangeRequired)
            {
                // If avoidance, instantly skip
                if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance)
                {
                    //vlastSafeSpot = vNullLocation;
                    bForceTargetUpdate = true;
                    bAvoidDirectionBlacklisting = false;
                    return RunStatus.Running;
                }

                if (settings.bDebugInfo)
                {
                    sStatusText = "[GilesTrinity] ";
                    switch (targetCurrent.ThisGilesObjectType)
                    {
                        case GilesObjectType.Avoidance:
                            sStatusText += "Avoid ";
                            break;
                        case GilesObjectType.Unit:
                            sStatusText += "Attack ";
                            break;
                        case GilesObjectType.Item:
                        case GilesObjectType.Gold:
                        case GilesObjectType.Globe:
                            sStatusText += "Pickup ";
                            break;
                        case GilesObjectType.Backtrack:
                            sStatusText += "Backtrack ";
                            break;
                        case GilesObjectType.Interactable:
                            sStatusText += "Interact ";
                            break;
                        case GilesObjectType.Container:
                            sStatusText += "Open ";
                            break;
                        case GilesObjectType.Destructible:
                        case GilesObjectType.Barricade:
                            sStatusText += "Destroy ";
                            break;
                        case GilesObjectType.Shrine:
                            sStatusText += "Click ";
                            break;
                    }
                    sStatusText += "Target=" + targetCurrent.sThisInternalName + " {" + targetCurrent.iThisActorSNO + "}. C-Dist=" + Math.Round(targetCurrent.fCentreDistance, 2).ToString() + ". " +
                        "R-Dist=" + Math.Round(targetCurrent.fRadiusDistance, 2).ToString() + ". ";
                    if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && powerPrime.powerThis != SNOPower.None)
                        sStatusText += "Power=" + powerPrime.powerThis.ToString() + " (range " + fRangeRequired.ToString() + ") ";
                    sStatusText += "Weight=" + targetCurrent.dThisWeight.ToString() + " IN RANGE, NOW INTERACTING";
                    BotMain.StatusText = sStatusText;
                    bResetStatusText = true;
                }

                // An integer to log total interact attempts on a particular object or item
                int iInteractAttempts;

                switch (targetCurrent.ThisGilesObjectType)
                {
                    // ************************************************************************
                    // *****           Unit, use our primary power to attack              *****
                    // ************************************************************************
                    case GilesObjectType.Unit:
                        if (powerPrime.powerThis != SNOPower.None)
                        {
                            // Force waiting for global cooldown timer or long-animation abilities
                            if (powerPrime.iForceWaitLoopsBefore >= 1 || (powerPrime.bWaitWhileAnimating != SIGNATURE_SPAM && DateTime.Now.Subtract(lastGlobalCooldownUse).TotalMilliseconds <= 50))
                            {
                                //Logging.WriteDiagnostic("Debug: Force waiting BEFORE ability " + powerPrime.powerThis.ToString() + "...");
                                bWaitingForPower = true;
                                if (powerPrime.iForceWaitLoopsBefore >= 1)
                                    powerPrime.iForceWaitLoopsBefore--;
                                return RunStatus.Running;
                            }
                            bWaitingForPower = false;

                            // Wait while animating before an attack
                            if (powerPrime.bWaitWhileAnimating)
                                WaitWhileAnimating(5, false);

                            // Use the power
                            bool bUsePowerSuccess = false;
                            // Note that whirlwinds use an off-on-off-on to avoid spam
                            if (powerPrime.powerThis != SNOPower.Barbarian_Whirlwind && powerPrime.powerThis != SNOPower.DemonHunter_Strafe)
                            {
                                ZetaDia.Me.UsePower(powerPrime.powerThis, powerPrime.vTargetLocation, powerPrime.iTargetWorldID, powerPrime.iTargetGUID);
                                bUsePowerSuccess = true;
                                lastChangedZigZag = DateTime.Today;
                                vPositionLastZigZagCheck = Vector3.Zero;
                            }
                            else
                            {
                                // Special code to prevent whirlwind double-spam, this helps save fury
                                bool bUseThisLoop = powerPrime.powerThis != powerLastSnoPowerUsed;
                                if (!bUseThisLoop)
                                {
                                    //powerLastSnoPowerUsed = SNOPower.None;
                                    if (DateTime.Now.Subtract(dictAbilityLastUse[powerPrime.powerThis]).TotalMilliseconds >= 200)
                                        bUseThisLoop = true;
                                }
                                if (bUseThisLoop)
                                {
                                    ZetaDia.Me.UsePower(powerPrime.powerThis, powerPrime.vTargetLocation, powerPrime.iTargetWorldID, powerPrime.iTargetGUID);
                                    bUsePowerSuccess = true;
                                }
                            }

                            if (bUsePowerSuccess)
                            {
                                //Logging.Write(powerPrime.powerThis.ToString() + " used successfully");
                                dictAbilityLastUse[powerPrime.powerThis] = DateTime.Now;
                                lastGlobalCooldownUse = DateTime.Now;
                                powerLastSnoPowerUsed = powerPrime.powerThis;
                                powerPrime.powerThis = SNOPower.None;

                                // Wait for animating AFTER the attack
                                if (powerPrime.bWaitWhileAnimating)
                                    WaitWhileAnimating(3, false);
                            }
                            else
                            {
                                //Logging.Write(powerPrime.powerThis.ToString() + " reported failure");
                                //dictAbilityLastFailed[powerPrime.powerThis] = DateTime.Now;
                                //*Logging.WriteDiagnostic("GSDebug: Skill use apparently failed=" + powerPrime.powerThis.ToString() + ", against enemy: " + targetCurrent.sThisInternalName +
                                //    " (skill use range=" + powerPrime.iMinimumRange.ToString() + ", enemy centre range=" + targetCurrent.fCentreDistance.ToString() + ", radius range=" +
                                //    targetCurrent.fRadiusDistance.ToString() + " (radius=" + targetCurrent.fThisRadius.ToString() + ")");*/
                            }

                            // Wait for animating AFTER the attack
                            if (powerPrime.bWaitWhileAnimating)
                                WaitWhileAnimating(3, false);

                            bPickNewAbilities = true;

                            // Keep looking for monsters at "normal kill range" a few moments after we successfully attack a monster incase we can pull them into range
                            iKeepKillRadiusExtendedFor = 8;
                            iKeepLootRadiusExtendedFor = 8;

                            // if at full or nearly full health, see if we can raycast to it, if not, ignore it for 2000 ms
                            // K: this is the source of get all boss wait. we will check it later
                            //    temporary solution here, only blacklist it if it's not the only target.
                            if (targetCurrent.iThisHitPoints >= 0.9d && iAnythingWithinRange[RANGE_50] > 3)
                            {
                                if (!GilesCanRayCast(playerStatus.vCurrentPosition, targetCurrent.vThisPosition, NavCellFlags.AllowWalk))
                                {
                                    iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid;
                                    iIgnoreThisForLoops = 6;
                                    // Add this monster to our very short-term ignore list
                                    hashRGUIDTemporaryBlacklist.Add(targetCurrent.iThisRActorGuid);
                                    lastTemporaryBlacklist = DateTime.Now;
                                    bNeedClearTemporaryBlacklist = true;
                                }
                            }



                            // See if we should force a long wait AFTERWARDS, too

                            // Force waiting AFTER power use for certain abilities
                            bWaitingAfterPower = false;
                            if (powerPrime.iForceWaitLoopsAfter >= 1)
                            {
                                //Logging.WriteDiagnostic("Force waiting AFTER ability " + powerPrime.powerThis.ToString() + "...");
                                bWaitingAfterPower = true;
                            }

                            return RunStatus.Running;
                        }
                        return RunStatus.Running;
                    // ************************************************************************
                    // *****          Item, interact with it and log item stats           *****
                    // ************************************************************************
                    case GilesObjectType.Item:
                        // Check if we actually have room for this item first
                        Vector2 ValidLocation = FindValidBackpackLocation(true);
                        if (ValidLocation.X < 0 || ValidLocation.Y < 0)
                        {
                            Logging.Write("No more space to pickup a 2-slot item, town-run requested at next free moment.");
                            bGilesForcedVendoring = true;
                            return RunStatus.Running;
                        }

                        // Pick the item up the usepower way, and "blacklist" for a couple of loops
                        WaitWhileAnimating(12, true);
                        ZetaDia.Me.UsePower(SNOPower.Axe_Operate_Gizmo, Vector3.Zero, 0, targetCurrent.iThisACDGUID);
                        lastChangedZigZag = DateTime.Today;
                        vPositionLastZigZagCheck = Vector3.Zero;
                        iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid;
                        iIgnoreThisForLoops = 3;

                        // Store item pickup stats
                        if (!_hashsetItemPicksLookedAt.Contains(targetCurrent.iThisRActorGuid))
                        {
                            _hashsetItemPicksLookedAt.Add(targetCurrent.iThisRActorGuid);
                            GilesItemType thisgilesitemtype = DetermineItemType(targetCurrent.sThisInternalName, targetCurrent.ThisDBItemType, targetCurrent.ThisFollowerType);
                            GilesBaseItemType thisgilesbasetype = DetermineBaseType(thisgilesitemtype);
                            if (thisgilesbasetype == GilesBaseItemType.Armor || thisgilesbasetype == GilesBaseItemType.WeaponOneHand || thisgilesbasetype == GilesBaseItemType.WeaponTwoHand ||
                                thisgilesbasetype == GilesBaseItemType.WeaponRange || thisgilesbasetype == GilesBaseItemType.Jewelry || thisgilesbasetype == GilesBaseItemType.FollowerItem ||
                                thisgilesbasetype == GilesBaseItemType.Offhand)
                            {
                                int iThisQuality;
                                ItemsPickedStats.iTotal++;
                                if (targetCurrent.ThisQuality >= ItemQuality.Legendary)
                                    iThisQuality = QUALITYORANGE;
                                else if (targetCurrent.ThisQuality >= ItemQuality.Rare4)
                                    iThisQuality = QUALITYYELLOW;
                                else if (targetCurrent.ThisQuality >= ItemQuality.Magic1)
                                    iThisQuality = QUALITYBLUE;
                                else
                                    iThisQuality = QUALITYWHITE;
                                ItemsPickedStats.iTotalPerQuality[iThisQuality]++;
                                ItemsPickedStats.iTotalPerLevel[targetCurrent.iThisLevel]++;
                                ItemsPickedStats.iTotalPerQPerL[iThisQuality, targetCurrent.iThisLevel]++;
                            }
                            else if (thisgilesbasetype == GilesBaseItemType.Gem)
                            {
                                int iThisGemType = 0;
                                ItemsPickedStats.iTotalGems++;
                                if (thisgilesitemtype == GilesItemType.Topaz)
                                    iThisGemType = GEMTOPAZ;
                                if (thisgilesitemtype == GilesItemType.Ruby)
                                    iThisGemType = GEMRUBY;
                                if (thisgilesitemtype == GilesItemType.Emerald)
                                    iThisGemType = GEMEMERALD;
                                if (thisgilesitemtype == GilesItemType.Amethyst)
                                    iThisGemType = GEMAMETHYST;
                                ItemsPickedStats.iGemsPerType[iThisGemType]++;
                                ItemsPickedStats.iGemsPerLevel[targetCurrent.iThisLevel]++;
                                ItemsPickedStats.iGemsPerTPerL[iThisGemType, targetCurrent.iThisLevel]++;
                            }
                            else if (thisgilesitemtype == GilesItemType.HealthPotion)
                            {
                                ItemsPickedStats.iTotalPotions++;
                                ItemsPickedStats.iPotionsPerLevel[targetCurrent.iThisLevel]++;
                            }
                            else if (tmp_item_ThisGilesItemType == GilesItemType.InfernalKey)
                            {
                                ItemsPickedStats.iTotalInfernalKeys++;
                            }
                            // See if we should update the stats file
                            if (DateTime.Now.Subtract(ItemStatsLastPostedReport).TotalSeconds > 10)
                            {
                                ItemStatsLastPostedReport = DateTime.Now;
                                OutputReport();
                            }
                        }
                        WaitWhileAnimating(5, true);
                        // Count how many times we've tried interacting
                        if (!dictTotalInteractionAttempts.TryGetValue(targetCurrent.iThisRActorGuid, out iInteractAttempts))
                        {
                            dictTotalInteractionAttempts.Add(targetCurrent.iThisRActorGuid, 1);
                        }
                        else
                        {
                            dictTotalInteractionAttempts[targetCurrent.iThisRActorGuid]++;
                        }
                        // If we've tried interacting too many times, blacklist this for a while
                        if (iInteractAttempts > 20)
                        {
                            hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid);
                            dateSinceTemporaryBlacklistClear = DateTime.Now;
                        }
                        // Now tell Trinity to get a new target!
                        bForceTargetUpdate = true;
                        return RunStatus.Running;
                    // ************************************************************************
                    // ***** Gold & Globe - need to get within pickup radius only         *****
                    // ************************************************************************
                    case GilesObjectType.Gold:
                    case GilesObjectType.Globe:
                        // Count how many times we've tried interacting
                        if (!dictTotalInteractionAttempts.TryGetValue(targetCurrent.iThisRActorGuid, out iInteractAttempts))
                        {
                            dictTotalInteractionAttempts.Add(targetCurrent.iThisRActorGuid, 1);
                        }
                        else
                        {
                            dictTotalInteractionAttempts[targetCurrent.iThisRActorGuid]++;
                        }
                        // If we've tried interacting too many times, blacklist this for a while
                        if (iInteractAttempts > 3)
                        {
                            hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid);
                            dateSinceTemporaryBlacklistClear = DateTime.Now;
                            hashRGUIDIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid);
                        }
                        iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid;
                        iIgnoreThisForLoops = 3;
                        // Now tell Trinity to get a new target!
                        lastChangedZigZag = DateTime.Today;
                        vPositionLastZigZagCheck = Vector3.Zero;
                        bForceTargetUpdate = true;
                        return RunStatus.Running;
                    // ************************************************************************
                    // ***** Shrine & Container - need to get within 8 feet and interact  *****
                    // ************************************************************************
                    case GilesObjectType.Shrine:
                    case GilesObjectType.Container:
                    case GilesObjectType.Interactable:
                        WaitWhileAnimating(5, true);
                        ZetaDia.Me.UsePower(SNOPower.Axe_Operate_Gizmo, Vector3.Zero, 0, targetCurrent.iThisACDGUID);
                        iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid;
                        iIgnoreThisForLoops = 2;
                        // Interactables can have a long channeling time...
                        if (targetCurrent.ThisGilesObjectType == GilesObjectType.Interactable)
                            WaitWhileAnimating(1500, true);
                        else
                            WaitWhileAnimating(12, true);
                        if (targetCurrent.ThisGilesObjectType == GilesObjectType.Interactable)
                        {
                            iIgnoreThisForLoops = 30;
                            hashRGUIDIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid);
                        }
                        // Count how many times we've tried interacting
                        if (!dictTotalInteractionAttempts.TryGetValue(targetCurrent.iThisRActorGuid, out iInteractAttempts))
                        {
                            dictTotalInteractionAttempts.Add(targetCurrent.iThisRActorGuid, 1);
                        }
                        else
                        {
                            dictTotalInteractionAttempts[targetCurrent.iThisRActorGuid]++;
                        }
                        // If we've tried interacting too many times, blacklist this for a while
                        if (iInteractAttempts > 5 || (targetCurrent.ThisGilesObjectType == GilesObjectType.Interactable && iInteractAttempts > 3))
                        {
                            hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid);
                            dateSinceTemporaryBlacklistClear = DateTime.Now;
                        }
                        // Now tell Trinity to get a new target!
                        lastChangedZigZag = DateTime.Today;
                        vPositionLastZigZagCheck = Vector3.Zero;
                        bForceTargetUpdate = true;
                        return RunStatus.Running;
                    // ************************************************************************
                    // ***** Destructible - need to pick an ability and attack it         *****
                    // ************************************************************************
                    case GilesObjectType.Destructible:
                    case GilesObjectType.Barricade:
                        if (powerPrime.powerThis != SNOPower.None)
                        {
                            if (targetCurrent.ThisGilesObjectType == GilesObjectType.Barricade)
                                Logging.WriteDiagnostic("[GilesTrinity] Barricade: Name=" + targetCurrent.sThisInternalName + ". SNO=" + targetCurrent.iThisActorSNO.ToString() +
                                    ", Range=" + targetCurrent.fCentreDistance.ToString() + ". Needed range=" + fRangeRequired.ToString() + ". Radius=" +
                                    targetCurrent.fThisRadius.ToString() + ". Type=" + targetCurrent.ThisGilesObjectType.ToString() + ". Using power=" + powerPrime.powerThis.ToString());
                            else
                                Logging.WriteDiagnostic("[GilesTrinity] Destructible: Name=" + targetCurrent.sThisInternalName + ". SNO=" + targetCurrent.iThisActorSNO.ToString() +
                                    ", Range=" + targetCurrent.fCentreDistance.ToString() + ". Needed range=" + fRangeRequired.ToString() + ". Radius=" +
                                    targetCurrent.fThisRadius.ToString() + ". Type=" + targetCurrent.ThisGilesObjectType.ToString() + ". Using power=" + powerPrime.powerThis.ToString());
                            WaitWhileAnimating(12, true);
                            if (targetCurrent.iThisRActorGuid == iIgnoreThisRactorGUID || hashDestructableLocationTarget.Contains(targetCurrent.iThisActorSNO))
                            {
                                // Location attack - attack the Vector3/map-area (equivalent of holding shift and left-clicking the object in-game to "force-attack")
                                Vector3 vAttackPoint;
                                if (targetCurrent.fCentreDistance >= 6f)
                                    vAttackPoint = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, 6f);
                                else
                                    vAttackPoint = targetCurrent.vThisPosition;
                                vAttackPoint.Z += 1.5f;
                                Logging.WriteDiagnostic("[GilesTrinity] (NB: Attacking location of destructable)");
                                ZetaDia.Me.UsePower(powerPrime.powerThis, vAttackPoint, iCurrentWorldID, -1);
                            }
                            else
                            {
                                // Standard attack - attack the ACDGUID (equivalent of left-clicking the object in-game)
                                ZetaDia.Me.UsePower(powerPrime.powerThis, vNullLocation, -1, targetCurrent.iThisACDGUID);
                            }
                            // Count how many times we've tried interacting
                            if (!dictTotalInteractionAttempts.TryGetValue(targetCurrent.iThisRActorGuid, out iInteractAttempts))
                            {
                                dictTotalInteractionAttempts.Add(targetCurrent.iThisRActorGuid, 1);
                            }
                            else
                            {
                                dictTotalInteractionAttempts[targetCurrent.iThisRActorGuid]++;
                            }
                            // If we've tried interacting too many times, blacklist this for a while
                            if (iInteractAttempts > 3)
                            {
                                hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid);
                                dateSinceTemporaryBlacklistClear = DateTime.Now;
                            }
                            dictAbilityLastUse[powerPrime.powerThis] = DateTime.Now;
                            powerPrime.powerThis = SNOPower.None;
                            WaitWhileAnimating(6, true);
                            // Prevent this EXACT object being targetted again for a short while, just incase
                            iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid;
                            iIgnoreThisForLoops = 3;
                            // Add this destructible/barricade to our very short-term ignore list
                            hashRGUIDDestructibleBlacklist.Add(targetCurrent.iThisRActorGuid);
                            lastDestroyedDestructible = DateTime.Now;
                            bNeedClearDestructibles = true;
                        }
                        // Now tell Trinity to get a new target!
                        bForceTargetUpdate = true;
                        lastChangedZigZag = DateTime.Today;
                        vPositionLastZigZagCheck = Vector3.Zero;
                        return RunStatus.Running;
                    // ************************************************************************
                    // ***** Backtrack - clear this waypoint                              *****
                    // ************************************************************************
                    case GilesObjectType.Backtrack:
                        // Remove the current backtrack location now we reached it
                        vBacktrackList.Remove(iTotalBacktracks);
                        iTotalBacktracks--;
                        // Never bother with the very first backtrack location
                        if (iTotalBacktracks <= 1)
                        {
                            iTotalBacktracks = 0;
                            vBacktrackList = new SortedList<int, Vector3>();
                        }
                        bForceTargetUpdate = true;
                        return RunStatus.Running;
                }

                return RunStatus.Running;
            }

            // ************************************************************************
            // *****          Out-of-range, so move towards the target            *****
            // ************************************************************************

            if (settings.bDebugInfo)
            {
                sStatusText = "[GilesTrinity] ";
                switch (targetCurrent.ThisGilesObjectType)
                {
                    case GilesObjectType.Avoidance:
                        sStatusText += "Avoid ";
                        break;
                    case GilesObjectType.Unit:
                        sStatusText += "Attack ";
                        break;
                    case GilesObjectType.Item:
                    case GilesObjectType.Gold:
                    case GilesObjectType.Globe:
                        sStatusText += "Pickup ";
                        break;
                    case GilesObjectType.Backtrack:
                        sStatusText += "Backtrack ";
                        break;
                    case GilesObjectType.Interactable:
                        sStatusText += "Interact ";
                        break;
                    case GilesObjectType.Container:
                        sStatusText += "Open ";
                        break;
                    case GilesObjectType.Destructible:
                    case GilesObjectType.Barricade:
                        sStatusText += "Destroy ";
                        break;
                    case GilesObjectType.Shrine:
                        sStatusText += "Click ";
                        break;
                }
                sStatusText += "Target=" + targetCurrent.sThisInternalName + " {" + targetCurrent.iThisActorSNO + "}. C-Dist=" + Math.Round(targetCurrent.fCentreDistance, 2).ToString() + ". " +
                        "R-Dist=" + Math.Round(targetCurrent.fRadiusDistance, 2).ToString() + ". ";
                if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && powerPrime.powerThis != SNOPower.None)
                    sStatusText += "Power=" + powerPrime.powerThis.ToString() + " (range " + fRangeRequired.ToString() + ") ";
                sStatusText += "Weight=" + targetCurrent.dThisWeight.ToString() + " MOVING INTO RANGE";
                BotMain.StatusText = sStatusText;
                bResetStatusText = true;
            }

            // Are we currently incapacitated? If so then wait...
            if (playerStatus.bIsIncapacitated || playerStatus.bIsRooted)
            {
                return RunStatus.Running;
            }

            // Some stuff to avoid spamming usepower EVERY loop, and also to detect stucks/staying in one place for too long
            bool bForceNewMovement = false;
            // Count how long we have failed to move - body block stuff etc.

            if (fDistanceFromTarget == fLastDistanceFromTarget)
            {
                bForceNewMovement = true;
                if (DateTime.Now.Subtract(lastMovedDuringCombat).TotalMilliseconds >= 250)
                {
                    lastMovedDuringCombat = DateTime.Now;
                    // We've been stuck at least 250 ms, let's go and pick new targets etc.
                    iTimesBlockedMoving++;
                    bForceCloseRangeTarget = true;
                    lastForcedKeepCloseRange = DateTime.Now;
                    // And tell Trinity to get a new target
                    bForceTargetUpdate = true;
                    // Blacklist an 80 degree direction for avoidance
                    if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance)
                    {
                        bAvoidDirectionBlacklisting = true;
                        fAvoidBlacklistDirection = FindDirectionDegree(playerStatus.vCurrentPosition, targetCurrent.vThisPosition);
                    }
                    // Tell target finder to prioritize close-combat targets incase we were bodyblocked
                    switch (iTimesBlockedMoving)
                    {
                        case 1:
                            iMillisecondsForceCloseRange = 850;
                            break;
                        case 2:
                            iMillisecondsForceCloseRange = 1300;
                            // Cancel avoidance attempts for 500ms
                            iMillisecondsCancelledEmergencyMoveFor = 1500;
                            timeCancelledEmergencyMove = DateTime.Now;
                            vlastSafeSpot = vNullLocation;
                            // Check for raycastability against objects
                            switch (targetCurrent.ThisGilesObjectType)
                            {
                                case GilesObjectType.Container:
                                case GilesObjectType.Shrine:
                                case GilesObjectType.Globe:
                                case GilesObjectType.Gold:
                                case GilesObjectType.Item:
                                    // No raycast available, try and force-ignore this for a little while, and blacklist for a few seconds
                                    if (!GilesCanRayCast(playerStatus.vCurrentPosition, targetCurrent.vThisPosition, NavCellFlags.AllowWalk))
                                    {
                                        iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid;
                                        iIgnoreThisForLoops = 6;
                                        hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid);
                                        dateSinceTemporaryBlacklistClear = DateTime.Now;
                                    }
                                    break;
                            }
                            break;
                        case 3:
                            iMillisecondsForceCloseRange = 2000;
                            // Cancel avoidance attempts for 1.5 seconds
                            iMillisecondsCancelledEmergencyMoveFor = 2000;
                            timeCancelledEmergencyMove = DateTime.Now;
                            vlastSafeSpot = vNullLocation;
                            // Blacklist the current avoidance target area for the next avoidance-spot find
                            if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance)
                                hashAvoidanceBlackspot.Add(new GilesObstacle(targetCurrent.vThisPosition, 12f, -1, 0));
                            break;
                        default:
                            iMillisecondsForceCloseRange = 4000;
                            // Cancel avoidance attempts for 3.5 seconds
                            iMillisecondsCancelledEmergencyMoveFor = 4000;
                            timeCancelledEmergencyMove = DateTime.Now;
                            vlastSafeSpot = vNullLocation;
                            // Blacklist the current avoidance target area for the next avoidance-spot find
                            if (iTimesBlockedMoving == 4 && targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance)
                                hashAvoidanceBlackspot.Add(new GilesObstacle(targetCurrent.vThisPosition, 16f, -1, 0));
                            break;
                    }
                    // If we were backtracking and failed, remove the current backtrack and try and move to the next
                    if (targetCurrent.ThisGilesObjectType == GilesObjectType.Backtrack && iTimesBlockedMoving >= 2)
                    {
                        vBacktrackList.Remove(iTotalBacktracks);
                        iTotalBacktracks--;
                        if (iTotalBacktracks <= 1)
                        {
                            iTotalBacktracks = 0;
                            vBacktrackList = new SortedList<int, Vector3>();
                        }
                    }
                    // Reset the emergency loop counter and return success
                    return RunStatus.Running;
                } // Been 250 milliseconds of non-movement?
            }
            else
            {
                // Movement has been made, so count the time last moved!
                lastMovedDuringCombat = DateTime.Now;
            }

            // Update the last distance stored
            fLastDistanceFromTarget = fDistanceFromTarget;

            // See if there's an obstacle in our way, if so try to navigate around it
            if (targetCurrent.ThisGilesObjectType != GilesObjectType.Avoidance)
            {
                Vector3 point = vCurrentDestination;
                foreach (GilesTrinity.GilesObstacle tempobstacle in GilesTrinity.hashNavigationObstacleCache.Where(cp =>
                                GilesTrinity.GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point) &&
                                cp.vThisLocation.Distance(playerStatus.vCurrentPosition) > GilesTrinity.dictSNONavigationSize[cp.iThisSNOID]))
                {
                    if (vShiftedPosition == Vector3.Zero)
                    {
                        if (DateTime.Now.Subtract(lastShiftedPosition).TotalSeconds >= 10)
                        {
                            float fDirectionToTarget = GilesTrinity.FindDirectionDegree(playerStatus.vCurrentPosition, vCurrentDestination);
                            vCurrentDestination = MathEx.GetPointAt(playerStatus.vCurrentPosition, 15f, MathEx.ToRadians(fDirectionToTarget - 50));
                            if (!GilesCanRayCast(playerStatus.vCurrentPosition, vCurrentDestination, NavCellFlags.AllowWalk))
                            {
                                vCurrentDestination = MathEx.GetPointAt(playerStatus.vCurrentPosition, 15f, MathEx.ToRadians(fDirectionToTarget + 50));
                                if (!GilesCanRayCast(playerStatus.vCurrentPosition, vCurrentDestination, NavCellFlags.AllowWalk))
                                {
                                    vCurrentDestination = point;
                                }
                            }
                            if (vCurrentDestination != point)
                            {
                                vShiftedPosition = vCurrentDestination;
                                iShiftPositionFor = 1000;
                                lastShiftedPosition = DateTime.Now;
                                Logging.WriteDiagnostic("[GilesTrinity] Mid-Target-Handle position shift location to: " + vCurrentDestination.ToString() + " (was " + point.ToString() + ")");
                            }
                        } // Make sure we only shift max once every 10 seconds
                    }
                    else
                    {
                        if (DateTime.Now.Subtract(lastShiftedPosition).TotalMilliseconds <= iShiftPositionFor)
                        {
                            vCurrentDestination = vShiftedPosition;
                        }
                        else
                        {
                            vShiftedPosition = Vector3.Zero;
                        }
                    }
                } // Position shifting code
            } // Only position-shift when not avoiding

            // See if we want to ACTUALLY move, or are just waiting for the last move command...
            if (!bForceNewMovement && bAlreadyMoving && vCurrentDestination == vLastMoveToTarget && DateTime.Now.Subtract(lastMovementCommand).TotalMilliseconds <= 100)
            {
                return RunStatus.Running;
            }

            // If we're doing avoidance, globes or backtracking, try to use special abilities to move quicker
            if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance ||
                targetCurrent.ThisGilesObjectType == GilesObjectType.Globe ||
                (targetCurrent.ThisGilesObjectType == GilesObjectType.Backtrack && settings.bOutOfCombatMovementPowers))
            {
                // Log whether we used a special movement (for avoidance really)
                bool bFoundSpecialMovement = false;
                // Leap movement for a barb
                if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Leap) &&
                    DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Leap]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Barbarian_Leap] &&
                    PowerManager.CanCast(SNOPower.Barbarian_Leap))
                {
                    WaitWhileAnimating(3, true);
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_Leap, vCurrentDestination, iCurrentWorldID, -1);
                    dictAbilityLastUse[SNOPower.Barbarian_Leap] = DateTime.Now;
                    bFoundSpecialMovement = true;
                }
                // Furious Charge movement for a barb
                if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_FuriousCharge) &&
                    DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Barbarian_FuriousCharge] &&
                    PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge))
                {
                    WaitWhileAnimating(3, true);
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_FuriousCharge, vCurrentDestination, iCurrentWorldID, -1);
                    dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge] = DateTime.Now;
                    bFoundSpecialMovement = true;
                }
                // Vault for a Demon Hunter
                if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Vault) &&
                    DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.DemonHunter_Vault]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.DemonHunter_Vault] &&
                    PowerManager.CanCast(SNOPower.DemonHunter_Vault))
                {
                    WaitWhileAnimating(3, true);
                    ZetaDia.Me.UsePower(SNOPower.DemonHunter_Vault, vCurrentDestination, iCurrentWorldID, -1);
                    dictAbilityLastUse[SNOPower.DemonHunter_Vault] = DateTime.Now;
                    bFoundSpecialMovement = true;
                }
                // Teleport for a wizard (need to be able to check skill rune in DB for a 3-4 teleport spam in a row)
                if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Teleport) &&
                    DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Wizard_Teleport]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Wizard_Teleport] &&
                    playerStatus.dCurrentEnergy >= 15 &&
                    PowerManager.CanCast(SNOPower.Wizard_Teleport))
                {
                    WaitWhileAnimating(3, true);
                    ZetaDia.Me.UsePower(SNOPower.Wizard_Teleport, vCurrentDestination, iCurrentWorldID, -1);
                    dictAbilityLastUse[SNOPower.Wizard_Teleport] = DateTime.Now;
                    bFoundSpecialMovement = true;
                }
                // Archon Teleport for a wizard (need to be able to check skill rune in DB for a 3-4 teleport spam in a row)
                if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon_Teleport) &&
                    DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Wizard_Archon_Teleport] &&
                    PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport))
                {
                    WaitWhileAnimating(3, true);
                    ZetaDia.Me.UsePower(SNOPower.Wizard_Archon_Teleport, vCurrentDestination, iCurrentWorldID, -1);
                    dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport] = DateTime.Now;
                    bFoundSpecialMovement = true;
                }
                if (targetCurrent.ThisGilesObjectType != GilesObjectType.Backtrack)
                {
                    // Whirlwind for a barb
                    //intell
                    if (!bWaitingForSpecial && powerPrime.powerThis != SNOPower.Barbarian_WrathOfTheBerserker && !bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && playerStatus.dCurrentEnergy >= 10)
                    {
                        ZetaDia.Me.UsePower(SNOPower.Barbarian_Whirlwind, vCurrentDestination, iCurrentWorldID, -1);
                        // Store the current destination for comparison incase of changes next loop
                        vLastMoveToTarget = vCurrentDestination;
                        // Reset total body-block count, since we should have moved
                        if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000)
                            iTimesBlockedMoving = 0;
                        return RunStatus.Running;
                    }
                    // Tempest rush for a monk
                    if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Monk_TempestRush) && playerStatus.dCurrentEnergy >= 20)
                    {
                        ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, vCurrentDestination, iCurrentWorldID, -1);
                        // Store the current destination for comparison incase of changes next loop
                        vLastMoveToTarget = vCurrentDestination;
                        // Reset total body-block count, since we should have moved
                        if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000)
                            iTimesBlockedMoving = 0;
                        return RunStatus.Running;
                    }
                    // Strafe for a Demon Hunter
                    if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Strafe) && playerStatus.dCurrentEnergy >= 15)
                    {
                        ZetaDia.Me.UsePower(SNOPower.DemonHunter_Strafe, vCurrentDestination, iCurrentWorldID, -1);
                        // Store the current destination for comparison incase of changes next loop
                        vLastMoveToTarget = vCurrentDestination;
                        // Reset total body-block count, since we should have moved
                        if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000)
                            iTimesBlockedMoving = 0;
                        return RunStatus.Running;
                    }
                }
                if (bFoundSpecialMovement)
                {
                    WaitWhileAnimating(6, true);
                    // Store the current destination for comparison incase of changes next loop
                    vLastMoveToTarget = vCurrentDestination;
                    // Reset total body-block count, since we should have moved
                    if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000)
                        iTimesBlockedMoving = 0;
                    return RunStatus.Running;
                }
            }

            // Whirlwind against everything within range (except backtrack points)
            //intell
            if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && playerStatus.dCurrentEnergy >= 10 && iAnythingWithinRange[RANGE_20] >= 1 && !bWaitingForSpecial && powerPrime.powerThis != SNOPower.Barbarian_WrathOfTheBerserker &&
                fDistanceFromTarget <= 12f && targetCurrent.ThisGilesObjectType != GilesObjectType.Container && targetCurrent.ThisGilesObjectType != GilesObjectType.Backtrack &&
                (!hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Sprint) || GilesHasBuff(SNOPower.Barbarian_Sprint)) &&
                targetCurrent.ThisGilesObjectType != GilesObjectType.Backtrack &&
                (targetCurrent.ThisGilesObjectType != GilesObjectType.Item && targetCurrent.ThisGilesObjectType != GilesObjectType.Gold && fDistanceFromTarget >= 6f) &&
                (targetCurrent.ThisGilesObjectType != GilesObjectType.Unit ||
                (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && !targetCurrent.bThisTreasureGoblin &&
                    (!settings.bSelectiveWhirlwind || bAnyNonWWIgnoreMobsInRange || !hashActorSNOWhirlwindIgnore.Contains(targetCurrent.iThisActorSNO)))))
            {
                // Special code to prevent whirlwind double-spam, this helps save fury
                bool bUseThisLoop = SNOPower.Barbarian_Whirlwind != powerLastSnoPowerUsed;
                if (!bUseThisLoop)
                {
                    powerLastSnoPowerUsed = SNOPower.None;
                    if (DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Whirlwind]).TotalMilliseconds >= 200)
                        bUseThisLoop = true;
                }
                if (bUseThisLoop)
                {
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_Whirlwind, vCurrentDestination, iCurrentWorldID, -1);
                    powerLastSnoPowerUsed = SNOPower.Barbarian_Whirlwind;
                    dictAbilityLastUse[SNOPower.Barbarian_Whirlwind] = DateTime.Now;
                }
                // Store the current destination for comparison incase of changes next loop
                vLastMoveToTarget = vCurrentDestination;
                // Reset total body-block count
                if ((!bForceCloseRangeTarget || DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds > iMillisecondsForceCloseRange) &&
                    DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000)
                    iTimesBlockedMoving = 0;
                return RunStatus.Running;
            }


            // Now for the actual movement request stuff
            bAlreadyMoving = true;
            lastMovementCommand = DateTime.Now;
            if (DateTime.Now.Subtract(lastSentMovePower).TotalMilliseconds >= 250 || Vector3.Distance(vLastMoveToTarget, vCurrentDestination) >= 2f || bForceNewMovement)
            {
                ZetaDia.Me.UsePower(SNOPower.Walk, vCurrentDestination, iCurrentWorldID, -1);
                lastSentMovePower = DateTime.Now;
                // Store the current destination for comparison incase of changes next loop
                vLastMoveToTarget = vCurrentDestination;
                // Reset total body-block count, since we should have moved
                if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000)
                    iTimesBlockedMoving = 0;
            }
            return RunStatus.Running;
        } // GilesMoveToTarget()

        // When did we last send a move-power command?
        private static DateTime lastSentMovePower = DateTime.Today;








        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // *****              Timers for abilities and selection of best ability etc.               *****
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************

        // **********************************************************************************************
        // *****                       Dictionaries holding the data/timers                         *****
        // **********************************************************************************************

        private static readonly Dictionary<SNOPower, int> dictAbilityRepeatDefaults = new Dictionary<SNOPower, int>
            {
                {SNOPower.DrinkHealthPotion, 30000}, 
                {SNOPower.Weapon_Melee_Instant, 5}, 
                {SNOPower.Weapon_Ranged_Instant, 5}, 
                //barn skills
                {SNOPower.Barbarian_Bash, 5}, 
                {SNOPower.Barbarian_Cleave, 5}, 
                {SNOPower.Barbarian_Frenzy, 5}, 
                {SNOPower.Barbarian_HammerOfTheAncients, 150}, 
                {SNOPower.Barbarian_Rend, 2650}, 
                {SNOPower.Barbarian_SeismicSlam, 200}, 
                {SNOPower.Barbarian_Whirlwind, 5}, 
                {SNOPower.Barbarian_GroundStomp, 12200}, 
                {SNOPower.Barbarian_Leap, 10200}, 
                {SNOPower.Barbarian_Sprint, 5000}, 
                {SNOPower.Barbarian_IgnorePain, 30200},
                {SNOPower.Barbarian_AncientSpear, 300}, // Has a rune that resets cooldown from 10 seconds to 0 on crit
                {SNOPower.Barbarian_Revenge, 600}, 
                {SNOPower.Barbarian_FuriousCharge, 500}, // Need to be able to check skill-rune for the dynamic cooldown - set to 10 always except for the skill rune :(
                {SNOPower.Barbarian_Overpower, 200}, 
                {SNOPower.Barbarian_WeaponThrow, 5}, 
                {SNOPower.Barbarian_ThreateningShout, 10200}, 
                {SNOPower.Barbarian_BattleRage, 118000}, 
                {SNOPower.Barbarian_WarCry, 20500}, 
                {SNOPower.Barbarian_Earthquake, 120500},  // Need to be able to check skill-run for dynamic cooldown, and passive for extra cooldown
                {SNOPower.Barbarian_CallOfTheAncients, 120500}, // Need to be able to check passive for cooldown
                {SNOPower.Barbarian_WrathOfTheBerserker, 90500},
                // Monk skills
                {SNOPower.Monk_FistsofThunder, 5},
                {SNOPower.Monk_BlindingFlash, 15200}, 
                {SNOPower.Monk_BreathOfHeaven, 15200}, 
                {SNOPower.Monk_Serenity, 20200}, 
                {SNOPower.Monk_SweepingWind, 5700}, 
                {SNOPower.Monk_MantraOfConviction, 3300}, 
            };
        // Actual delays copy the defaults
        public static Dictionary<SNOPower, int> dictAbilityRepeatDelay = new Dictionary<SNOPower, int>(dictAbilityRepeatDefaults);
        // Last used-timers for all abilities to prevent spamming D3 memory for cancast checks too often
        // These should NEVER need manually editing
        // But you do need to make sure every skill used by Trinity is listed in here once!
        private static Dictionary<SNOPower, DateTime> dictAbilityLastUseDefaults = new Dictionary<SNOPower, DateTime>
            {
                {SNOPower.DrinkHealthPotion, DateTime.Today},{SNOPower.Weapon_Melee_Instant, DateTime.Today},{SNOPower.Weapon_Ranged_Instant, DateTime.Today}, 
                // Barbarian last-used timers
                {SNOPower.Barbarian_Bash, DateTime.Today},{SNOPower.Barbarian_Cleave, DateTime.Today},{SNOPower.Barbarian_Frenzy, DateTime.Today}, 
                {SNOPower.Barbarian_HammerOfTheAncients, DateTime.Today},{SNOPower.Barbarian_Rend, DateTime.Today},{SNOPower.Barbarian_SeismicSlam, DateTime.Today}, 
                {SNOPower.Barbarian_Whirlwind, DateTime.Today},{SNOPower.Barbarian_GroundStomp, DateTime.Today},{SNOPower.Barbarian_Leap, DateTime.Today}, 
                {SNOPower.Barbarian_Sprint, DateTime.Today},{SNOPower.Barbarian_IgnorePain, DateTime.Today},{SNOPower.Barbarian_AncientSpear, DateTime.Today},
                {SNOPower.Barbarian_Revenge, DateTime.Today},{SNOPower.Barbarian_FuriousCharge, DateTime.Today},{SNOPower.Barbarian_Overpower, DateTime.Today}, 
                {SNOPower.Barbarian_WeaponThrow, DateTime.Today},{SNOPower.Barbarian_ThreateningShout, DateTime.Today},{SNOPower.Barbarian_BattleRage, DateTime.Today},
                {SNOPower.Barbarian_WarCry, DateTime.Today},{SNOPower.Barbarian_Earthquake, DateTime.Today},{SNOPower.Barbarian_CallOfTheAncients, DateTime.Today}, 
                {SNOPower.Barbarian_WrathOfTheBerserker, DateTime.Today }, 
                // Monk last-used timers
                {SNOPower.Monk_FistsofThunder, DateTime.Today},{SNOPower.Monk_BlindingFlash, DateTime.Today},{SNOPower.Monk_BreathOfHeaven, DateTime.Today}, 
                {SNOPower.Monk_Serenity, DateTime.Today},{SNOPower.Monk_SweepingWind, DateTime.Today}, {SNOPower.Monk_MantraOfConviction, DateTime.Today}, 
            };
        // This is the ACTUAL dictionary used now (the above are used to quickly reset all timers back to defaults on death etc.)
        public static Dictionary<SNOPower, DateTime> dictAbilityLastUse = new Dictionary<SNOPower, DateTime>(dictAbilityLastUseDefaults);
        // And this is to avoid using certain long-cooldown skills immediately after a fail
        public static Dictionary<SNOPower, DateTime> dictAbilityLastFailed = new Dictionary<SNOPower, DateTime>(dictAbilityLastUseDefaults);
        // And a "global cooldown" to prevent non-signature-spells being used too fast
        public static DateTime lastGlobalCooldownUse = DateTime.Today;

        // Used only for certain skills that spam the powermanager regularly, to limit their CPU hits
        private static Dictionary<SNOPower, DateTime> dictAbilityLastPowerChecked = new Dictionary<SNOPower, DateTime>
            {
                {SNOPower.Barbarian_Revenge, DateTime.Today}, 
                {SNOPower.Barbarian_FuriousCharge, DateTime.Today},
                {SNOPower.Wizard_DiamondSkin, DateTime.Today},
                {SNOPower.Wizard_FrostNova, DateTime.Today},
                {SNOPower.Wizard_ExplosiveBlast, DateTime.Today},
                {SNOPower.Witchdoctor_Hex, DateTime.Today},
                {SNOPower.Witchdoctor_SoulHarvest, DateTime.Today},
            };

        // **********************************************************************************************
        // *****                         Check re-use timers on skills                             *****
        // **********************************************************************************************

        // Returns whether or not we can use a skill, or if it's on our own internal Trinity cooldown timer
        private static bool GilesUseTimer(SNOPower thispower, bool bReCheck = false)
        {
            if (DateTime.Now.Subtract(dictAbilityLastUse[thispower]).TotalMilliseconds >= dictAbilityRepeatDelay[thispower])
                return true;
            if (bReCheck && DateTime.Now.Subtract(dictAbilityLastUse[thispower]).TotalMilliseconds >= 150 && DateTime.Now.Subtract(dictAbilityLastUse[thispower]).TotalMilliseconds <= 600)
                return true;
            return false;
        }

        // This function checks when the spell last failed (according to D3 memory, which isn't always reliable)
        // To prevent Trinity getting stuck re-trying the same spell over and over and doing nothing else
        // No longer used but keeping this here incase I re-use it
        private static bool GilesCanRecastAfterFailure(SNOPower thispower, int iMaxRecheckTime = 250)
        {
            if (DateTime.Now.Subtract(dictAbilityLastFailed[thispower]).TotalMilliseconds <= iMaxRecheckTime)
                return false;
            return true;
        }

        // When last hit the power-manager for this - not currently used, saved here incase I use it again in the future!
        // This is a safety function to prevent spam of the CPU and time-intensive "PowerManager.CanCast" function in DB
        // No longer used but keeping this here incase I re-use it
        private static bool GilesPowerManager(SNOPower thispower, int iMaxRecheckTime)
        {
            if (DateTime.Now.Subtract(dictAbilityLastPowerChecked[thispower]).TotalMilliseconds <= iMaxRecheckTime)
                return false;
            dictAbilityLastPowerChecked[thispower] = DateTime.Now;
            if (PowerManager.CanCast(thispower))
                return true;
            return false;
        }

        // **********************************************************************************************
        // *****                    Checking for buffs and caching the buff list                    *****
        // **********************************************************************************************

        // Cache all current buffs on character
        public static void GilesRefreshBuffs()
        {
            listCachedBuffs = new List<Buff>();
            dictCachedBuffs = new Dictionary<int, int>();
            listCachedBuffs = ZetaDia.Me.GetAllBuffs().ToList();
            
            int iTempStackCount;
            // Store how many stacks of each buff we have
            foreach (Buff thisbuff in listCachedBuffs)
            {
                // Store the stack count of this buff
                if (!dictCachedBuffs.TryGetValue(thisbuff.SNOId, out iTempStackCount))
                    dictCachedBuffs.Add(thisbuff.SNOId, thisbuff.StackCount);
                
            }
            
        }

        // Check if a particular buff is present
        public static bool GilesHasBuff(SNOPower power)
        {
            int id = (int)power;
            return listCachedBuffs.Any(u => u.SNOId == id);
        }

        // Returns how many stacks of a particular buff there are
        public static int GilesBuffStacks(SNOPower thispower)
        {
            int iStacks;
            if (dictCachedBuffs.TryGetValue((int)thispower, out iStacks))
            {
                return iStacks;
            }
            return 0;
        }

        // Refresh the skills in our hotbar
        // Also caches the values after - but ONLY if we aren't in archon mode (or if this function is told NOT to cache this)
        public static void GilesRefreshHotbar(bool bDontCacheThis = false)
        {
            bMappedPlayerAbilities = true;
            hashPowerHotbarAbilities = new HashSet<SNOPower>();
            for (int i = 0; i <= 5; i++)
                hashPowerHotbarAbilities.Add(ZetaDia.Me.GetHotbarPowerId((HotbarSlot)i));
            bRefreshHotbarAbilities = false;
            if (!bDontCacheThis)
                hashCachedPowerHotbarAbilities = new HashSet<SNOPower>(hashPowerHotbarAbilities);
        }

        // **********************************************************************************************
        // *****                        Now Find the best ability to use                            *****
        // **********************************************************************************************

        public static GilesPower GilesAbilitySelector(bool bCurrentlyAvoiding = false, bool bOOCBuff = false, bool bDestructiblePower = false)
        {
           // Refresh buffs once to save buff-check-spam
            GilesRefreshBuffs();
            

            // Switch based on the cached character class
            switch (iMyCachedActorClass)
            {
                // *****************
                // ***** Barbs *****
                // *****************
                case ActorClass.Barbarian:
                    // Pick the best destructible power available
                    if (bDestructiblePower)
                    {
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Frenzy))
                            return new GilesPower(SNOPower.Barbarian_Frenzy, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY);
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Bash))
                            return new GilesPower(SNOPower.Barbarian_Bash, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY);
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Cleave))
                            return new GilesPower(SNOPower.Barbarian_Cleave, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY);
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Rend) && playerStatus.dCurrentEnergyPct >= 0.65)
                            return new GilesPower(SNOPower.Barbarian_Rend, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY);
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WeaponThrow) && playerStatus.dCurrentEnergy >= 20)
                            return new GilesPower(SNOPower.Barbarian_WeaponThrow, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY);
                        return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY);
                    }

                                    
                    // Flag up a variable to see if we should reserve 50 fury for special abilities
                    bWaitingForSpecial = false;

                    if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WrathOfTheBerserker) && PowerManager.CanCast(SNOPower.Barbarian_WrathOfTheBerserker) &&
                        GilesUseTimer(SNOPower.Barbarian_WrathOfTheBerserker))
                    {
                        Logging.Write("[Trinity] WOTB is ready to cast");
                        bWaitingForSpecial = true;
                    }

                             
                    // Wrath of the berserker
                    //intell
                    if (!bOOCBuff && bWaitingForSpecial &&
                        !GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker) &&
                        GilesUseTimer(SNOPower.Barbarian_WrathOfTheBerserker) && playerStatus.dCurrentEnergy >= 50
                        )
                    {
                           bWaitingForSpecial = false;
                           return new GilesPower(SNOPower.Barbarian_WrathOfTheBerserker, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); //intell -- 4, 4
                    }

                    // Battle rage, for if being followed and before we do sprint
                    if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) &&
                        GilesUseTimer(SNOPower.Barbarian_BattleRage) && !GilesHasBuff(SNOPower.Barbarian_BattleRage) &&
                        playerStatus.dCurrentEnergy >= 20 && !playerStatus.bIsIncapacitated
                        )
                    {
                        return new GilesPower(SNOPower.Barbarian_BattleRage, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY);
                    }
                   
                    //sprint v2
                   
                    if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Sprint) &&
                        GilesUseTimer(SNOPower.Barbarian_Sprint) && !GilesHasBuff(SNOPower.Barbarian_Sprint) &&
                        playerStatus.dCurrentEnergy >= 20 && !playerStatus.bIsIncapacitated
                        )
                    {
                        return new GilesPower(SNOPower.Barbarian_Sprint, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY);
                    }

                    // Rend spam
                    if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Rend) &&
                        //iAnythingWithinRange[RANGE_12] >= 1 &&
                        (iAnythingWithinRange[RANGE_15] > 2) &&
                        (DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Rend]).TotalMilliseconds >= 900) &&
                        (playerStatus.dCurrentHealthPct <= 0.8 || targetCurrent.iThisHitPoints >= 0.80)
                        )
                    {
                        iWithinRangeLastRend = iAnythingWithinRange[RANGE_6];
                        iACDGUIDLastRend = targetCurrent.iThisACDGUID;
                        // Note - we have LONGER animation times for whirlwind-users
                        // Since whirlwind seems to interrupt rend so easily
                        int iPreDelay = 0;
                        int iPostDelay = 0;
                        return new GilesPower(SNOPower.Barbarian_Rend, 0f, playerStatus.vCurrentPosition, iCurrentWorldID, -1, iPreDelay, iPostDelay, USE_SLOWLY);
                    }

                    // Hammer of the ancients spam-attacks - never use if waiting for special
                    if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_HammerOfTheAncients) &&
                       // GilesUseTimer(SNOPower.Barbarian_HammerOfTheAncients) &&
                        playerStatus.dCurrentEnergyPct >= 0.5 && !playerStatus.bIsIncapacitated
                        )
                    {
                        return new GilesPower(SNOPower.Barbarian_HammerOfTheAncients, 12f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY);
                    }


                    // Frenzy rapid-attacks
                    if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Frenzy))
                    {
                        return new GilesPower(SNOPower.Barbarian_Frenzy, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM);
                    }

                    // Bash fast-attacks
                    if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Bash) && playerStatus.dCurrentEnergyPct < 0.5)
                    {
                        return new GilesPower(SNOPower.Barbarian_Bash, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY);
                    }
                   
                    // Default attacks
                    if (!bOOCBuff && !bCurrentlyAvoiding && playerStatus.dCurrentEnergy < 20)
                    {
                        Logging.Write("[Debug] Basic attack");
                        return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY);
                    }
                    break;
                case ActorClass.Monk:
                    // Pick the best destructible power available
                    if (bDestructiblePower)
                    {
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_FistsofThunder))
                            return new GilesPower(SNOPower.Monk_FistsofThunder, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY);
                    }

                    // Initial buff (slow-use)             
                    if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) && !GilesHasBuff(SNOPower.Monk_MantraOfConviction) &&
                        playerStatus.dCurrentEnergy >= 50 && GilesUseTimer(SNOPower.Monk_MantraOfConviction, true))
                    {
                        return new GilesPower(SNOPower.Monk_MantraOfConviction, 0f, vNullLocation, iCurrentWorldID, -1, 0, 1, USE_SLOWLY);
                    }
                    // Breath of heaven when needing healing or the buff
                    if (!bOOCBuff && (playerStatus.dCurrentHealthPct <= 0.6 || !GilesHasBuff(SNOPower.Monk_BreathOfHeaven)) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_BreathOfHeaven) &&
                        playerStatus.dCurrentEnergy >= 25 && GilesUseTimer(SNOPower.Monk_BreathOfHeaven))
                    {
                        return new GilesPower(SNOPower.Monk_BreathOfHeaven, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY);
                    }
                    // Blinding Flash
                    if (!bOOCBuff && playerStatus.dCurrentEnergy >= 20 && hashPowerHotbarAbilities.Contains(SNOPower.Monk_BlindingFlash) &&
                         (iElitesWithinRange[RANGE_15] > 0 || playerStatus.dCurrentHealthPct <= 0.4 || iAnythingWithinRange[RANGE_15] > 0 || (iAnythingWithinRange[RANGE_15] >= 1 && playerStatus.dCurrentEnergyPct >= 0.7) || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 15f)) &&
                         GilesUseTimer(SNOPower.Monk_BlindingFlash))
                    {
                        return new GilesPower(SNOPower.Monk_BlindingFlash, 11f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY);
                    }
                    // Blinding Flash as a DEFENSE
                    if (!bOOCBuff && playerStatus.dCurrentEnergy >= 10 && hashPowerHotbarAbilities.Contains(SNOPower.Monk_BlindingFlash) &&
                        playerStatus.dCurrentHealthPct <= 0.25 && iAnythingWithinRange[RANGE_15] >= 1 &&
                        GilesUseTimer(SNOPower.Monk_BlindingFlash))
                    {
                        return new GilesPower(SNOPower.Monk_BlindingFlash, 11f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY);
                    }
                    // Sweeping wind
                    if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Monk_SweepingWind) && !GilesHasBuff(SNOPower.Monk_SweepingWind) &&
                        (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_20] >= 1 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 25f)) &&
                        // Check our mantras, if we have them, are up first
                        (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfEvasion) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfEvasion) && GilesHasBuff(SNOPower.Monk_MantraOfEvasion))) &&
                        (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) && GilesHasBuff(SNOPower.Monk_MantraOfConviction))) &&
                        (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfRetribution) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfRetribution) && GilesHasBuff(SNOPower.Monk_MantraOfRetribution))) &&
                        // Check the re-use timer and energy costs
                        (playerStatus.dCurrentEnergy >= 75 || (settings.bMonkInnaSet && playerStatus.dCurrentEnergy >= 5)) && GilesUseTimer(SNOPower.Monk_SweepingWind))
                    {
                        return new GilesPower(SNOPower.Monk_SweepingWind, 0f, vNullLocation, iCurrentWorldID, -1, 2, 2, USE_SLOWLY);
                    }

                    // Serenity if health is low
                    if ((playerStatus.dCurrentHealthPct <= 0.50 || (playerStatus.bIsIncapacitated && playerStatus.dCurrentHealthPct <= 0.50)) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_Serenity) &&
                        GilesUseTimer(SNOPower.Monk_Serenity, true) &&
                        playerStatus.dCurrentEnergy >= 10)
                    {
                        return new GilesPower(SNOPower.Monk_Serenity, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY);
                    }

                    // 4 Mantra spam for the 4 second buff
                    if (!bOOCBuff && (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_TempestRush) || playerStatus.dCurrentEnergy >= 98 ||
                        (playerStatus.dCurrentHealthPct <= 0.55 && playerStatus.dCurrentEnergy >= 75) || targetCurrent.bThisBoss) &&
                            (playerStatus.dCurrentEnergy >= 135 ||
                            (GilesHasBuff(SNOPower.Monk_SweepingWind) && (playerStatus.dCurrentEnergy >= 60 ||
                            (playerStatus.dCurrentEnergy >= 50 && playerStatus.dCurrentHealthPct >= 0.6)) &&
                        // Checking we have no expensive finishers
                            !hashPowerHotbarAbilities.Contains(SNOPower.Monk_SevenSidedStrike) && !hashPowerHotbarAbilities.Contains(SNOPower.Monk_LashingTailKick) &&
                            !hashPowerHotbarAbilities.Contains(SNOPower.Monk_WaveOfLight) && !hashPowerHotbarAbilities.Contains(SNOPower.Monk_CycloneStrike) &&
                            !hashPowerHotbarAbilities.Contains(SNOPower.Monk_ExplodingPalm))) &&
                        (iElitesWithinRange[RANGE_15] >= 1 || iAnythingWithinRange[RANGE_15] >= 3 ||
                        (iAnythingWithinRange[RANGE_15] >= 1 && (settings.bMonkInnaSet && playerStatus.dCurrentEnergy >= 70)))) //intell -- inna
                    {
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfEvasion) && GilesUseTimer(SNOPower.Monk_MantraOfEvasion))
                        {
                            return new GilesPower(SNOPower.Monk_MantraOfEvasion, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY);
                        }
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) && GilesUseTimer(SNOPower.Monk_MantraOfConviction))
                        {
                            return new GilesPower(SNOPower.Monk_MantraOfConviction, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY);
                        }
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfRetribution) && GilesUseTimer(SNOPower.Monk_MantraOfRetribution))
                        {
                            return new GilesPower(SNOPower.Monk_MantraOfRetribution, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY);
                        }
                        if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfHealing) && GilesUseTimer(SNOPower.Monk_MantraOfHealing))
                        {
                            return new GilesPower(SNOPower.Monk_MantraOfHealing, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY);
                        }
                    }

                    // Fists of thunder as the primary, repeatable attack
                    if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Monk_FistsofThunder))
                    {
                        if (GilesHasBuff(SNOPower.Monk_SweepingWind))
                            SweepWindSpam = DateTime.Now; //intell -- inna
                        return new GilesPower(SNOPower.Monk_FistsofThunder, 30f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM);
                    }
                    break;
            }
           
           
                    
           
                    
           
            return new GilesPower(SNOPower.None, 0, vNullLocation, -1, -1, 0, 0, false);
        }


        // **********************************************************************************************
        // *****                Quick Easy Raycast Function for quick changes                       *****
        // **********************************************************************************************
        public static bool GilesCanRayCast(Vector3 vStartLocation, Vector3 vDestination, NavCellFlags NavType = NavCellFlags.AllowWalk)
        {
            if (ZetaDia.Physics.Raycast(new Vector3(vStartLocation.X, vStartLocation.Y, vStartLocation.Z + 3f), new Vector3(vDestination.X, vDestination.Y, vDestination.Z + 3f), NavType))
                return true;
            return false;
        }


        // **********************************************************************************************
        // *****                            Calculate direction of A to B                           *****
        // **********************************************************************************************
        // Quickly calculate the direction a vector is from ourselves, and return it as a float
        public static float FindDirectionDegree(Vector3 vStartLocation, Vector3 vTargetLocation)
        {
            return (float)RadianToDegree(NormalizeRadian((float)Math.Atan2(vTargetLocation.Y - vStartLocation.Y, vTargetLocation.X - vStartLocation.X)));
        }




        // **********************************************************************************************
        // *****                            Find A Safe Movement Location                           *****
        // **********************************************************************************************

        private static bool bAvoidDirectionBlacklisting = false;
        private static float fAvoidBlacklistDirection = 0f;

        public static Vector3 FindSafeZone(bool bFindAntiStuckSpot, int iAntiStuckAttempts, Vector3 vNearbyPoint, bool bKitingSpot = false)
        {
            if (!bFindAntiStuckSpot)
            {
                // Already searched & found a safe spot in last 800 milliseconds, stick to it
                if (!bTravellingAvoidance && DateTime.Now.Subtract(lastFoundSafeSpot).TotalMilliseconds <= 800 && vlastSafeSpot != vNullLocation)
                {
                    return vlastSafeSpot;
                }

                bHasEmergencyTeleportUp = (
                    // Leap is available
                    (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Leap) &&
                        DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Leap]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Barbarian_Leap]) ||
                    // Whirlwind is available
                    (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) &&
                        ((playerStatus.dCurrentEnergy >= 10 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) ||
                    // Tempest rush is available
                    (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Monk_TempestRush) &&
                        ((playerStatus.dCurrentEnergy >= 20 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) ||
                    // Teleport is available
                    (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Teleport) &&
                        playerStatus.dCurrentEnergy >= 15 &&
                        PowerManager.CanCast(SNOPower.Wizard_Teleport)) ||
                    // Archon Teleport is available
                    (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon_Teleport) &&
                        PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport))
                    );

                // Wizards can look for bee stings in range and try a wave of force to dispel them
                if (!bKitingSpot && iMyCachedActorClass == ActorClass.Wizard && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_WaveOfForce) && playerStatus.dCurrentEnergy >= 25 &&
                    DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Wizard_WaveOfForce]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Wizard_WaveOfForce] &&
                    !playerStatus.bIsIncapacitated && hashAvoidanceObstacleCache.Count(u => u.iThisSNOID == 5212 && u.vThisLocation.Distance(playerStatus.vCurrentPosition) <= 15f) >= 2 &&
                    (settings.bEnableCriticalMass || PowerManager.CanCast(SNOPower.Wizard_WaveOfForce)))
                {
                    ZetaDia.Me.UsePower(SNOPower.Wizard_WaveOfForce, vNullLocation, iCurrentWorldID, -1);
                }
            } // Only looking for an anti-stuck location?

            // Now find a randomized safe point we can actually move to
            // We randomize the order so we don't spam walk by accident back and forth
            Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber));
            int iFakeStart = (rndNum.Next(36)) * 10;
            float fHighestWeight = 0f;
            Vector3 vBestLocation = vNullLocation;
            // Start off checking every 12 degrees (which is 30 loops for a full circle)
            const int iMaxRadiusChecks = 30;
            const int iRadiusMultiplier = 12;
            for (int iStep = 1; iStep <= 8; iStep++)
            {
                // Distance of 10 for each step loop at first
                int iDistanceOut = 10;
                if (!bKitingSpot)
                {
                    switch (iStep)
                    {
                        case 1: iDistanceOut = 10; break;
                        case 2: iDistanceOut = 18; break;
                        case 3: iDistanceOut = 26; break;
                        case 4: iDistanceOut = 34; break;
                        case 5: iDistanceOut = 42; break;
                        case 6: iDistanceOut = 50; break;
                        case 7: iDistanceOut = 58; break;
                        case 8: iDistanceOut = 66; break;
                    }
                }
                else
                {
                    switch (iStep)
                    {
                        case 8: iDistanceOut = 10; break;
                        case 7: iDistanceOut = 18; break;
                        case 6: iDistanceOut = 26; break;
                        case 5: iDistanceOut = 34; break;
                        case 4: iDistanceOut = 42; break;
                        case 3: iDistanceOut = 50; break;
                        case 2: iDistanceOut = 58; break;
                        case 1: iDistanceOut = 66; break;
                    }
                }
                int iRandomUse = 3 + ((iStep - 1) * 4);
                // Try to return "early", or as soon as possible, beyond step 4, except when unstucking, when the max steps is based on the unstuck attempt
                if (fHighestWeight > 0 &&
                    ((!bFindAntiStuckSpot && iStep > 5) || (bFindAntiStuckSpot && iStep > iAntiStuckAttempts))
                    )
                {
                    lastFoundSafeSpot = DateTime.Now;
                    vlastSafeSpot = vBestLocation;
                    break;
                }
                // Loop through all possible radii
                for (int iThisRadius = 0; iThisRadius < iMaxRadiusChecks; iThisRadius++)
                {
                    int iPosition = iFakeStart + (iThisRadius * iRadiusMultiplier);
                    if (iPosition >= 360)
                        iPosition -= 360;

                    float fBonusAmount = 0f;

                    // See if we've blacklisted a 70 degree arc around this direction
                    if (bAvoidDirectionBlacklisting)
                    {
                        if (Math.Abs(fAvoidBlacklistDirection - iPosition) <= 35 || Math.Abs(fAvoidBlacklistDirection - iPosition) >= 325)
                            continue;
                        if (Math.Abs(fAvoidBlacklistDirection - iPosition) >= 145 || Math.Abs(fAvoidBlacklistDirection - iPosition) <= 215)
                            fBonusAmount = 200f;
                    }

                    Vector3 vTestPoint = MathEx.GetPointAt(playerStatus.vCurrentPosition, iDistanceOut, MathEx.ToRadians(iPosition));
                    // First check no avoidance obstacles in this spot
                    if (!hashAvoidanceObstacleCache.Any(u => u.vThisLocation.Distance(vTestPoint) <= dictAvoidanceRadius[u.iThisSNOID]))
                    {
                        bool bAvoidBlackspot = hashAvoidanceBlackspot.Any(cp => Vector3.Distance(cp.vThisLocation, vTestPoint) <= cp.fThisRadius);
                        bool bCanRaycast = false;
                        if (!bAvoidBlackspot)
                            bCanRaycast = GilesCanRayCast(playerStatus.vCurrentPosition, vTestPoint, NavCellFlags.AllowWalk);
                        // Now see if the client can navigate there, and we haven't temporarily blacklisted this spot
                        if (!bAvoidBlackspot && bCanRaycast)
                        {
                            // Now calculate a weight to pick the "best" avoidance safety spot at the moment
                            float fThisWeight = 1000f + fBonusAmount;
                            if (!bFindAntiStuckSpot)
                            {
                                fThisWeight -= ((iStep - 1) * 150);
                            }
                            // is it near a point we'd prefer to be close to?
                            if (vNearbyPoint != vNullLocation)
                            {
                                float fDistanceToNearby = Vector3.Distance(vTestPoint, vNearbyPoint);
                                if (fDistanceToNearby <= 25f)
                                {
                                    if (!bKitingSpot)
                                        fThisWeight += (160 * (1 - (fDistanceToNearby / 25)));
                                    else
                                        fThisWeight -= (300 * (1 - (fDistanceToNearby / 25)));
                                }
                            }
                            // See if we should check for avoidance spots and monsters in the pathing
                            if (!bFindAntiStuckSpot)
                            {
                                Vector3 point = vTestPoint;
                                int iMonsterCount = hashMonsterObstacleCache.Count(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point));
                                fThisWeight -= (iMonsterCount * 30);
                                foreach (GilesObstacle tempobstacle in hashAvoidanceObstacleCache.Where(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point)))
                                {
                                    fThisWeight -= (float)(tempobstacle.dThisWeight * 0.6);
                                }
                            }
                            if (bKitingSpot)
                            {
                                // Kiting spots don't like to end up near other monsters
                                foreach (GilesObstacle tempobstacle in hashMonsterObstacleCache.Where(cp => Vector3.Distance(cp.vThisLocation, vTestPoint) <= (cp.fThisRadius * 1.2)))
                                {
                                    fThisWeight -= 30;
                                }
                            }
                            // Give extra weight to areas we've been inside before
                            bool bExtraSafetyWeight = hashSkipAheadAreaCache.Any(cp => cp.vThisLocation.Distance(vTestPoint) <= cp.fThisRadius);
                            if (bExtraSafetyWeight)
                            {
                                if (bKitingSpot)
                                {
                                    fThisWeight += 350f;
                                }
                                else if (bFindAntiStuckSpot)
                                {
                                    fThisWeight += 300f;
                                }
                                else
                                {
                                    fThisWeight += 100f;
                                }
                            }

                            if (fThisWeight <= 1)
                                fThisWeight = 1;
                            // Use this one if it's more weight, or we haven't even found one yet, or if same weight as another with a random chance
                            if (fThisWeight > fHighestWeight || fHighestWeight == 0f || (fThisWeight == fHighestWeight && rndNum.Next(iRandomUse) == 1))
                            {
                                fHighestWeight = fThisWeight;
                                vBestLocation = vTestPoint;
                                // Found a very good spot so just use this one!
                                //if (iAOECount == 0 && fThisWeight > 400)
                                //    break;
                            }
                        }
                    }
                } // Loop through the circle
            } // Loop through distance-range steps
            if (fHighestWeight > 0)
            {
                lastFoundSafeSpot = DateTime.Now;
                vlastSafeSpot = vBestLocation;
            }
            return vBestLocation;
        }


        // **********************************************************************************************
        // *****                       Check if an obstacle is blocking our path                    *****
        // **********************************************************************************************
        public static bool GilesIntersectsPath(Vector3 obstacle, float radius, Vector3 start, Vector3 destination)
        {
            float fDirectionToTarget = NormalizeRadian((float)Math.Atan2(destination.Y - start.Y, destination.X - start.X));
            float fDirectionToObstacle = NormalizeRadian((float)Math.Atan2(obstacle.Y - start.Y, obstacle.X - start.X));
            if (Math.Abs(RadianToDegree(fDirectionToTarget) - RadianToDegree(fDirectionToObstacle)) > 30)
            {
                return false;
            }
            if (radius <= 1f) radius = 1f;
            if (radius >= 15f) radius = 15f;
            Ray ray = new Ray(start, Vector3.NormalizedDirection(start, destination));
            Sphere sphere = new Sphere(obstacle, radius);
            float? nullable = ray.Intersects(sphere);
            return (nullable.HasValue && (nullable.Value < start.Distance(destination)));
        }
        public static float NormalizeRadian(float radian)
        {
            if (radian < 0)
            {
                double mod = -radian;
                mod %= Math.PI * 2d;
                mod = -mod + Math.PI * 2d;
                return (float)mod;
            }
            return (float)(radian % (Math.PI * 2d));
        }
        public static double RadianToDegree(double angle)
        {
            return angle * (180.0 / Math.PI);
        }




















        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // *****                              Item handling Stuff                                   *****
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************
        // **********************************************************************************************














        // **********************************************************************************************
        // *****                Randomize the timer between stashing/salvaging etc.                 *****
        // **********************************************************************************************

        private static void RandomizeTheTimer()
        {
            Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber));
            int rnd = rndNum.Next(7);
            iItemDelayLoopLimit = 4 + rnd;
        }

        // **********************************************************************************************
        // *****       Pickup Validation - Determines what should or should not be picked up        *****
        // **********************************************************************************************

        private static bool GilesPickupItemValidation(string tempname, int templevel, ItemQuality tempquality, int tempbalanceid, ItemType thisdbitemtype, FollowerType thisfollowertype, int iDynamicID = 0)
        {
            // If it's legendary, we always want it *IF* it's level is right
            if (tempquality >= ItemQuality.Legendary)
            {
                if (settings.iFilterLegendary > 0 && (templevel >= settings.iFilterLegendary || settings.iFilterLegendary == 1))
                    return true;
                return false;
            }

            // Calculate giles item types and base types etc.
            GilesItemType thisGilesItemType = DetermineItemType(tempname, thisdbitemtype, thisfollowertype);
            GilesBaseItemType thisGilesBaseType = DetermineBaseType(thisGilesItemType);

            // Error logging for DemonBuddy item mis-reading
            ItemType gilesDBItemType = GilesToDBItemType(thisGilesItemType);
            if (gilesDBItemType != thisdbitemtype)
            {
                Log("GSError: Item type mis-match detected: Item Internal=" + tempname + ". DemonBuddy ItemType thinks item type is=" + thisdbitemtype.ToString() + ". Giles thinks item type is=" +
                    gilesDBItemType.ToString() + ". [pickup]", true);
            }

            switch (thisGilesBaseType)
            {
                case GilesBaseItemType.WeaponTwoHand:
                case GilesBaseItemType.WeaponOneHand:
                case GilesBaseItemType.WeaponRange:
                    // Not enough DPS, so analyse for possibility to blacklist
                    if (tempquality < ItemQuality.Magic1)
                    {
                        // White item, blacklist
                        return false;
                    }
                    if (tempquality >= ItemQuality.Magic1 && tempquality < ItemQuality.Rare4)
                    {
                        if (settings.iFilterBlueWeapons == 0 || (settings.iFilterBlueWeapons != 0 && templevel < settings.iFilterBlueWeapons))
                        {
                            // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want
                            return false;
                        }
                    }
                    else
                    {
                        if (settings.iFilterYellowWeapons == 0 || (settings.iFilterYellowWeapons != 0 && templevel < settings.iFilterYellowWeapons))
                        {
                            // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want
                            return false;
                        }
                    }
                    break;
                case GilesBaseItemType.Armor:
                case GilesBaseItemType.Offhand:
                    if (tempquality < ItemQuality.Magic1)
                    {
                        // White item, blacklist
                        return false;
                    }
                    if (tempquality >= ItemQuality.Magic1 && tempquality < ItemQuality.Rare4)
                    {
                        if (settings.iFilterBlueArmor == 0 || (settings.iFilterBlueArmor != 0 && templevel < settings.iFilterBlueArmor))
                        {
                            // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want
                            return false;
                        }
                    }
                    else
                    {
                        if (settings.iFilterYellowArmor == 0 || (settings.iFilterYellowArmor != 0 && templevel < settings.iFilterYellowArmor))
                        {
                            // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want
                            return false;
                        }
                    }
                    break;
                case GilesBaseItemType.Jewelry:
                    if (tempquality < ItemQuality.Magic1)
                    {
                        // White item, blacklist
                        return false;
                    }
                    if (tempquality >= ItemQuality.Magic1 && tempquality < ItemQuality.Rare4)
                    {
                        if (settings.iFilterBlueJewelry == 0 || (settings.iFilterBlueJewelry != 0 && templevel < settings.iFilterBlueJewelry))
                        {
                            // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want
                            return false;
                        }
                    }
                    else
                    {
                        if (settings.iFilterYellowJewelry == 0 || (settings.iFilterYellowJewelry != 0 && templevel < settings.iFilterYellowJewelry))
                        {
                            // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want
                            return false;
                        }
                    }
                    break;
                case GilesBaseItemType.FollowerItem:
                    if (templevel < 60 || !settings.bPickupFollower || tempquality < ItemQuality.Rare4)
                    {
                        if (!_hashsetItemFollowersIgnored.Contains(iDynamicID))
                        {
                            _hashsetItemFollowersIgnored.Add(iDynamicID);
                            iTotalFollowerItemsIgnored++;
                        }
                        return false;
                    }
                    break;
                case GilesBaseItemType.Gem:
                    if (templevel < settings.iFilterGems || (thisGilesItemType == GilesItemType.Ruby && !settings.bGemsRuby) || (thisGilesItemType == GilesItemType.Emerald && !settings.bGemsEmerald) ||
                        (thisGilesItemType == GilesItemType.Amethyst && !settings.bGemsAmethyst) || (thisGilesItemType == GilesItemType.Topaz && !settings.bGemsTopaz))
                    {
                        return false;
                    }
                    break;
                case GilesBaseItemType.Misc:
                    // Note; Infernal keys are misc, so should be picked up here - we aren't filtering them out, so should default to true at the end of this function
                    if (thisGilesItemType == GilesItemType.CraftingMaterial && templevel < settings.iFilterMisc)
                    {
                        return false;
                    }
                    if (thisGilesItemType == GilesItemType.CraftTome && (templevel < settings.iFilterMisc || !settings.bPickupCraftTomes))
                    {
                        return false;
                    }
                    if (thisGilesItemType == GilesItemType.CraftingPlan && !settings.bPickupPlans)
                    {
                        return false;
                    }
                    // Potion filtering
                    if (thisGilesItemType == GilesItemType.HealthPotion)
                    {
                        if (settings.iFilterPotions == 1 || templevel < settings.iFilterPotionLevel)
                        {
                            return false;
                        }
                        if (settings.iFilterPotions == 2)
                        {
                            // Map out all the items already in the backpack
                            int iTotalPotions =
                                (from tempitem in ZetaDia.Me.Inventory.Backpack where tempitem.BaseAddress != IntPtr.Zero where tempitem.GameBalanceId == tempbalanceid select tempitem.ItemStackQuantity).Sum();
                            if (iTotalPotions > 98)
                            {
                                return false;
                            }
                        }
                    }
                    break;
                case GilesBaseItemType.HealthGlobe:
                    return false;
                case GilesBaseItemType.Unknown:
                    return false;
                default:
                    return false;
            } // Switch giles base item type
            // Didn't cancel it, so default to true!
            return true;
        }

        // **********************************************************************************************
        // *****      Sell Validation - Determines what should or should not be sold to vendor      *****
        // **********************************************************************************************
        private static bool GilesSellValidation(string thisinternalname, int thislevel, ItemQuality thisquality, ItemType thisdbitemtype, FollowerType thisfollowertype)
        {
            // Check this isn't something we want to salvage
            if (settings.bSalvageJunk)
            {
                if (GilesSalvageValidation(thisinternalname, thislevel, thisquality, thisdbitemtype, thisfollowertype))
                    return false;
            }

            // Make sure it's not legendary
            //if (thisquality >= ItemQuality.Legendary)
            //    return false;
            
            GilesItemType thisGilesItemType = DetermineItemType(thisinternalname, thisdbitemtype, thisfollowertype);
            GilesBaseItemType thisGilesBaseType = DetermineBaseType(thisGilesItemType);

            switch (thisGilesBaseType)
            {
                case GilesBaseItemType.WeaponRange:
                case GilesBaseItemType.WeaponOneHand:
                case GilesBaseItemType.WeaponTwoHand:
                case GilesBaseItemType.Armor:
                case GilesBaseItemType.Offhand:
                case GilesBaseItemType.Jewelry:
                case GilesBaseItemType.FollowerItem:
                    return true;
                case GilesBaseItemType.Gem:
                case GilesBaseItemType.Misc:
                case GilesBaseItemType.Unknown:
                    return false;
            } // Switch giles base item type
            return false;
        }

        // **********************************************************************************************
        // *****       Salvage Validation - Determines what should or should not be salvaged        *****
        // **********************************************************************************************

        private static bool GilesSalvageValidation(string thisinternalname, int thislevel, ItemQuality thisquality, ItemType thisdbitemtype, FollowerType thisfollowertype)
        {
            if (!settings.bSalvageJunk)
                return false;
            // Make sure it's not legendary
            //if (thisquality >= ItemQuality.Legendary)
            //    return false;
            
            GilesItemType thisGilesItemType = DetermineItemType(thisinternalname, thisdbitemtype, thisfollowertype);
            GilesBaseItemType thisGilesBaseType = DetermineBaseType(thisGilesItemType);

            switch (thisGilesBaseType)
            {
                case GilesBaseItemType.WeaponRange:
                case GilesBaseItemType.WeaponOneHand:
                case GilesBaseItemType.WeaponTwoHand:
                case GilesBaseItemType.Armor:
                case GilesBaseItemType.Offhand:
                    if (thislevel >= 61 && thisquality >= ItemQuality.Magic1)
                    {
                        return true;
                    }
                    return false;
                case GilesBaseItemType.Jewelry:
                    if (thislevel >= 59 && thisquality >= ItemQuality.Magic1)
                    {
                        return true;
                    }
                    return false;
                case GilesBaseItemType.FollowerItem:
                    if (thislevel >= 60 && thisquality >= ItemQuality.Magic1)
                    {
                        return true;
                    }
                    return false;
                case GilesBaseItemType.Gem:
                case GilesBaseItemType.Misc:
                case GilesBaseItemType.Unknown:
                    return false;
            } // Switch giles base item type
            return false;
        }

        // **********************************************************************************************
        // ***** DetermineItemType - Calculates what kind of item it is from D3 internalnames       *****
        // **********************************************************************************************
        private static GilesItemType DetermineItemType(string sThisInternalName, ItemType DBItemType, FollowerType dbFollowerType = FollowerType.None)
        {
            sThisInternalName = sThisInternalName.ToLower();
            if (sThisInternalName.StartsWith("axe_")) return GilesItemType.Axe;
            if (sThisInternalName.StartsWith("ceremonialdagger_")) return GilesItemType.CeremonialKnife;
            if (sThisInternalName.StartsWith("handxbow_")) return GilesItemType.HandCrossbow;
            if (sThisInternalName.StartsWith("dagger_")) return GilesItemType.Dagger;
            if (sThisInternalName.StartsWith("fistweapon_")) return GilesItemType.FistWeapon;
            if (sThisInternalName.StartsWith("mace_")) return GilesItemType.Mace;
            if (sThisInternalName.StartsWith("mightyweapon_1h_")) return GilesItemType.MightyWeapon;
            if (sThisInternalName.StartsWith("spear_")) return GilesItemType.Spear;
            if (sThisInternalName.StartsWith("sword_")) return GilesItemType.Sword;
            if (sThisInternalName.StartsWith("wand_")) return GilesItemType.Wand;
            if (sThisInternalName.StartsWith("twohandedaxe_")) return GilesItemType.TwoHandAxe;
            if (sThisInternalName.StartsWith("bow_")) return GilesItemType.TwoHandBow;
            if (sThisInternalName.StartsWith("combatstaff_")) return GilesItemType.TwoHandDaibo;
            if (sThisInternalName.StartsWith("xbow_")) return GilesItemType.TwoHandCrossbow;
            if (sThisInternalName.StartsWith("twohandedmace_")) return GilesItemType.TwoHandMace;
            if (sThisInternalName.StartsWith("mightyweapon_2h_")) return GilesItemType.TwoHandMighty;
            if (sThisInternalName.StartsWith("polearm_")) return GilesItemType.TwoHandPolearm;
            if (sThisInternalName.StartsWith("staff_")) return GilesItemType.TwoHandStaff;
            if (sThisInternalName.StartsWith("twohandedsword_")) return GilesItemType.TwoHandSword;
            if (sThisInternalName.StartsWith("staffofcow")) return GilesItemType.StaffOfHerding;
            if (sThisInternalName.StartsWith("mojo_")) return GilesItemType.Mojo;
            if (sThisInternalName.StartsWith("orb_")) return GilesItemType.Source;
            if (sThisInternalName.StartsWith("quiver_")) return GilesItemType.Quiver;
            if (sThisInternalName.StartsWith("shield_")) return GilesItemType.Shield;
            if (sThisInternalName.StartsWith("amulet_")) return GilesItemType.Amulet;
            if (sThisInternalName.StartsWith("ring_")) return GilesItemType.Ring;
            if (sThisInternalName.StartsWith("boots_")) return GilesItemType.Boots;
            if (sThisInternalName.StartsWith("bracers_")) return GilesItemType.Bracers;
            if (sThisInternalName.StartsWith("cloak_")) return GilesItemType.Cloak;
            if (sThisInternalName.StartsWith("gloves_")) return GilesItemType.Gloves;
            if (sThisInternalName.StartsWith("pants_")) return GilesItemType.Pants;
            if (sThisInternalName.StartsWith("barbbelt_")) return GilesItemType.MightyBelt;
            if (sThisInternalName.StartsWith("shoulderpads_")) return GilesItemType.Shoulders;
            if (sThisInternalName.StartsWith("spiritstone_")) return GilesItemType.SpiritStone;
            if (sThisInternalName.StartsWith("voodoomask_")) return GilesItemType.VoodooMask;
            if (sThisInternalName.StartsWith("wizardhat_")) return GilesItemType.WizardHat;
            if (sThisInternalName.StartsWith("lore_book_")) return GilesItemType.CraftTome;
            if (sThisInternalName.StartsWith("page_of_")) return GilesItemType.CraftTome;
            if (sThisInternalName.StartsWith("blacksmithstome")) return GilesItemType.CraftTome;
            if (sThisInternalName.StartsWith("ruby_")) return GilesItemType.Ruby;
            if (sThisInternalName.StartsWith("emerald_")) return GilesItemType.Emerald;
            if (sThisInternalName.StartsWith("topaz_")) return GilesItemType.Topaz;
            if (sThisInternalName.StartsWith("amethyst")) return GilesItemType.Amethyst;
            if (sThisInternalName.StartsWith("healthpotion_")) return GilesItemType.HealthPotion;
            if (sThisInternalName.StartsWith("followeritem_enchantress_")) return GilesItemType.FollowerEnchantress;
            if (sThisInternalName.StartsWith("followeritem_scoundrel_")) return GilesItemType.FollowerScoundrel;
            if (sThisInternalName.StartsWith("followeritem_templar_")) return GilesItemType.FollowerTemplar;
            if (sThisInternalName.StartsWith("craftingplan_")) return GilesItemType.CraftingPlan;
            if (sThisInternalName.StartsWith("dye_")) return GilesItemType.Dye;
            if (sThisInternalName.StartsWith("a1_")) return GilesItemType.SpecialItem;
            if (sThisInternalName.StartsWith("healthglobe")) return GilesItemType.HealthGlobe;

            // Follower item types
            if (sThisInternalName.StartsWith("jewelbox_") || DBItemType == ItemType.FollowerSpecial)
            {
                if (dbFollowerType == FollowerType.Scoundrel)
                    return GilesItemType.FollowerScoundrel;
                if (dbFollowerType == FollowerType.Templar)
                    return GilesItemType.FollowerTemplar;
                if (dbFollowerType == FollowerType.Enchantress)
                    return GilesItemType.FollowerEnchantress;
            }

            // Fall back on some partial DB item type checking 
            if (sThisInternalName.StartsWith("crafting_"))
            {
                if (DBItemType == ItemType.CraftingPage) return GilesItemType.CraftTome;
                return GilesItemType.CraftingMaterial;
            }

            if (sThisInternalName.StartsWith("chestarmor_"))
            {
                if (DBItemType == ItemType.Cloak) return GilesItemType.Cloak;
                return GilesItemType.Chest;
            }
            if (sThisInternalName.StartsWith("helm_"))
            {
                if (DBItemType == ItemType.SpiritStone) return GilesItemType.SpiritStone;
                if (DBItemType == ItemType.VoodooMask) return GilesItemType.VoodooMask;
                if (DBItemType == ItemType.WizardHat) return GilesItemType.WizardHat;
                return GilesItemType.Helm;
            }
            if (sThisInternalName.StartsWith("helmcloth_"))
            {
                if (DBItemType == ItemType.SpiritStone) return GilesItemType.SpiritStone;
                if (DBItemType == ItemType.VoodooMask) return GilesItemType.VoodooMask;
                if (DBItemType == ItemType.WizardHat) return GilesItemType.WizardHat;
                return GilesItemType.Helm;
            }
            if (sThisInternalName.StartsWith("belt_"))
            {
                if (DBItemType == ItemType.MightyBelt) return GilesItemType.MightyBelt;
                return GilesItemType.Belt;
            }
            if (sThisInternalName.StartsWith("demonkey_") || sThisInternalName.StartsWith("demontrebuchetkey"))
            {
                return GilesItemType.InfernalKey;
            }
            // ORGANS QUICK HACK IN
            if (sThisInternalName.StartsWith("quest_"))
            {
                return GilesItemType.InfernalKey;
            }

            return GilesItemType.Unknown;
        }

        // **********************************************************************************************
        // *****      DetermineBaseType - Calculates a more generic, "basic" type of item           *****
        // **********************************************************************************************

        private static GilesBaseItemType DetermineBaseType(GilesItemType thisGilesItemType)
        {
            GilesBaseItemType thisGilesBaseType = GilesBaseItemType.Unknown;
            if (thisGilesItemType == GilesItemType.Axe || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Dagger ||
                thisGilesItemType == GilesItemType.FistWeapon || thisGilesItemType == GilesItemType.Mace || thisGilesItemType == GilesItemType.MightyWeapon ||
                thisGilesItemType == GilesItemType.Spear || thisGilesItemType == GilesItemType.Sword || thisGilesItemType == GilesItemType.Wand)
            {
                thisGilesBaseType = GilesBaseItemType.WeaponOneHand;
            }
            else if (thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.TwoHandMace ||
                thisGilesItemType == GilesItemType.TwoHandMighty || thisGilesItemType == GilesItemType.TwoHandPolearm || thisGilesItemType == GilesItemType.TwoHandStaff ||
                thisGilesItemType == GilesItemType.TwoHandSword || thisGilesItemType == GilesItemType.TwoHandAxe)
            {
                thisGilesBaseType = GilesBaseItemType.WeaponTwoHand;
            }
            else if (thisGilesItemType == GilesItemType.TwoHandCrossbow || thisGilesItemType == GilesItemType.HandCrossbow || thisGilesItemType == GilesItemType.TwoHandBow)
            {
                thisGilesBaseType = GilesBaseItemType.WeaponRange;
            }
            else if (thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source ||
                thisGilesItemType == GilesItemType.Quiver || thisGilesItemType == GilesItemType.Shield)
            {
                thisGilesBaseType = GilesBaseItemType.Offhand;
            }
            else if (thisGilesItemType == GilesItemType.Boots || thisGilesItemType == GilesItemType.Bracers || thisGilesItemType == GilesItemType.Chest ||
                thisGilesItemType == GilesItemType.Cloak || thisGilesItemType == GilesItemType.Gloves || thisGilesItemType == GilesItemType.Helm ||
                thisGilesItemType == GilesItemType.Pants || thisGilesItemType == GilesItemType.Shoulders || thisGilesItemType == GilesItemType.SpiritStone ||
                thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.WizardHat || thisGilesItemType == GilesItemType.Belt ||
                thisGilesItemType == GilesItemType.MightyBelt)
            {
                thisGilesBaseType = GilesBaseItemType.Armor;
            }
            else if (thisGilesItemType == GilesItemType.Amulet || thisGilesItemType == GilesItemType.Ring)
            {
                thisGilesBaseType = GilesBaseItemType.Jewelry;
            }
            else if (thisGilesItemType == GilesItemType.FollowerEnchantress || thisGilesItemType == GilesItemType.FollowerScoundrel ||
                thisGilesItemType == GilesItemType.FollowerTemplar)
            {
                thisGilesBaseType = GilesBaseItemType.FollowerItem;
            }
            else if (thisGilesItemType == GilesItemType.CraftingMaterial || thisGilesItemType == GilesItemType.CraftTome ||
                thisGilesItemType == GilesItemType.SpecialItem || thisGilesItemType == GilesItemType.CraftingPlan || thisGilesItemType == GilesItemType.HealthPotion ||
                thisGilesItemType == GilesItemType.Dye || thisGilesItemType == GilesItemType.StaffOfHerding || thisGilesItemType == GilesItemType.InfernalKey)
            {
                thisGilesBaseType = GilesBaseItemType.Misc;
            }
            else if (thisGilesItemType == GilesItemType.Ruby || thisGilesItemType == GilesItemType.Emerald || thisGilesItemType == GilesItemType.Topaz ||
                thisGilesItemType == GilesItemType.Amethyst)
            {
                thisGilesBaseType = GilesBaseItemType.Gem;
            }
            else if (thisGilesItemType == GilesItemType.HealthGlobe)
            {
                thisGilesBaseType = GilesBaseItemType.HealthGlobe;
            }
            return thisGilesBaseType;
        }

        // **********************************************************************************************
        // *****          DetermineIsStackable - Calculates what items can be stacked up            *****
        // **********************************************************************************************

        private static bool DetermineIsStackable(GilesItemType thisGilesItemType)
        {
            bool bIsStackable = thisGilesItemType == GilesItemType.CraftingMaterial || thisGilesItemType == GilesItemType.CraftTome || thisGilesItemType == GilesItemType.Ruby ||
                                thisGilesItemType == GilesItemType.Emerald || thisGilesItemType == GilesItemType.Topaz || thisGilesItemType == GilesItemType.Amethyst ||
                                thisGilesItemType == GilesItemType.HealthPotion || thisGilesItemType == GilesItemType.CraftingPlan || thisGilesItemType == GilesItemType.Dye ||
                                thisGilesItemType == GilesItemType.InfernalKey;
            return bIsStackable;
        }

        // **********************************************************************************************
        // *****      DetermineIsTwoSlot - Tries to calculate what items take up 2 slots or 1       *****
        // **********************************************************************************************

        private static bool DetermineIsTwoSlot(GilesItemType thisGilesItemType)
        {
            if (thisGilesItemType == GilesItemType.Axe || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Dagger ||
                thisGilesItemType == GilesItemType.FistWeapon || thisGilesItemType == GilesItemType.Mace || thisGilesItemType == GilesItemType.MightyWeapon ||
                thisGilesItemType == GilesItemType.Spear || thisGilesItemType == GilesItemType.Sword || thisGilesItemType == GilesItemType.Wand ||
                thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.TwoHandCrossbow || thisGilesItemType == GilesItemType.TwoHandMace ||
                thisGilesItemType == GilesItemType.TwoHandMighty || thisGilesItemType == GilesItemType.TwoHandPolearm || thisGilesItemType == GilesItemType.TwoHandStaff ||
                thisGilesItemType == GilesItemType.TwoHandSword || thisGilesItemType == GilesItemType.TwoHandAxe || thisGilesItemType == GilesItemType.HandCrossbow ||
                thisGilesItemType == GilesItemType.TwoHandBow || thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source ||
                thisGilesItemType == GilesItemType.Quiver || thisGilesItemType == GilesItemType.Shield || thisGilesItemType == GilesItemType.Boots ||
                thisGilesItemType == GilesItemType.Bracers || thisGilesItemType == GilesItemType.Chest || thisGilesItemType == GilesItemType.Cloak ||
                thisGilesItemType == GilesItemType.Gloves || thisGilesItemType == GilesItemType.Helm || thisGilesItemType == GilesItemType.Pants ||
                thisGilesItemType == GilesItemType.Shoulders || thisGilesItemType == GilesItemType.SpiritStone ||
                thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.WizardHat || thisGilesItemType == GilesItemType.StaffOfHerding)
                return true;
            return false;
        }

        // **********************************************************************************************
        // *****   This is for DemonBuddy error checking - see what sort of item DB THINKS it is    *****
        // **********************************************************************************************

        private static ItemType GilesToDBItemType(GilesItemType thisgilesitemtype)
        {
            switch (thisgilesitemtype)
            {
                case GilesItemType.Axe: return ItemType.Axe;
                case GilesItemType.CeremonialKnife: return ItemType.CeremonialDagger;
                case GilesItemType.HandCrossbow: return ItemType.HandCrossbow;
                case GilesItemType.Dagger: return ItemType.Dagger;
                case GilesItemType.FistWeapon: return ItemType.FistWeapon;
                case GilesItemType.Mace: return ItemType.Mace;
                case GilesItemType.MightyWeapon: return ItemType.MightyWeapon;
                case GilesItemType.Spear: return ItemType.Spear;
                case GilesItemType.Sword: return ItemType.Sword;
                case GilesItemType.Wand: return ItemType.Wand;
                case GilesItemType.TwoHandAxe: return ItemType.Axe;
                case GilesItemType.TwoHandBow: return ItemType.Bow;
                case GilesItemType.TwoHandDaibo: return ItemType.Daibo;
                case GilesItemType.TwoHandCrossbow: return ItemType.Crossbow;
                case GilesItemType.TwoHandMace: return ItemType.Mace;
                case GilesItemType.TwoHandMighty: return ItemType.MightyWeapon;
                case GilesItemType.TwoHandPolearm: return ItemType.Polearm;
                case GilesItemType.TwoHandStaff: return ItemType.Staff;
                case GilesItemType.TwoHandSword: return ItemType.Sword;
                case GilesItemType.StaffOfHerding: return ItemType.Staff;
                case GilesItemType.Mojo: return ItemType.Mojo;
                case GilesItemType.Source: return ItemType.Orb;
                case GilesItemType.Quiver: return ItemType.Quiver;
                case GilesItemType.Shield: return ItemType.Shield;
                case GilesItemType.Amulet: return ItemType.Amulet;
                case GilesItemType.Ring: return ItemType.Ring;
                case GilesItemType.Belt: return ItemType.Belt;
                case GilesItemType.Boots: return ItemType.Boots;
                case GilesItemType.Bracers: return ItemType.Bracer;
                case GilesItemType.Chest: return ItemType.Chest;
                case GilesItemType.Cloak: return ItemType.Cloak;
                case GilesItemType.Gloves: return ItemType.Gloves;
                case GilesItemType.Helm: return ItemType.Helm;
                case GilesItemType.Pants: return ItemType.Legs;
                case GilesItemType.MightyBelt: return ItemType.MightyBelt;
                case GilesItemType.Shoulders: return ItemType.Shoulder;
                case GilesItemType.SpiritStone: return ItemType.SpiritStone;
                case GilesItemType.VoodooMask: return ItemType.VoodooMask;
                case GilesItemType.WizardHat: return ItemType.WizardHat;
                case GilesItemType.FollowerEnchantress: return ItemType.FollowerSpecial;
                case GilesItemType.FollowerScoundrel: return ItemType.FollowerSpecial;
                case GilesItemType.FollowerTemplar: return ItemType.FollowerSpecial;
                case GilesItemType.CraftingMaterial: return ItemType.CraftingReagent;
                case GilesItemType.CraftTome: return ItemType.CraftingPage;
                case GilesItemType.Ruby: return ItemType.Gem;
                case GilesItemType.Emerald: return ItemType.Gem;
                case GilesItemType.Topaz: return ItemType.Gem;
                case GilesItemType.Amethyst: return ItemType.Gem;
                case GilesItemType.SpecialItem: return ItemType.Unknown;
                case GilesItemType.CraftingPlan: return ItemType.CraftingPlan;
                case GilesItemType.HealthPotion: return ItemType.Potion;
                case GilesItemType.Dye: return ItemType.Unknown;
                case GilesItemType.InfernalKey: return ItemType.Unknown;
            }
            return ItemType.Unknown;
        }



        // **********************************************************************************************
        // *****         TownRunCheckOverlord - determine if we should do a town-run or not         *****
        // **********************************************************************************************

        private static bool GilesTownRunCheckOverlord(object ret)
        {
            bWantToTownRun = false;
            // Check if we should be forcing a town-run
            if (bGilesForcedVendoring || Zeta.CommonBot.Logic.BrainBehavior.IsVendoring)
            {
                if (!bLastTownRunCheckResult)
                {
                    bPreStashPauseDone = false;
                    if (Zeta.CommonBot.Logic.BrainBehavior.IsVendoring)
                    {
                        Log("Looks like we are being asked to force a town-run by a profile/plugin/new DB feature, now doing so.");
                    }
                }
                bWantToTownRun = true;
            }
            // Time safety switch for more advanced town-run checking to prevent CPU spam
            else if (DateTime.Now.Subtract(TimeLastCheckedForTownRun).TotalSeconds > 6)
            {
                TimeLastCheckedForTownRun = DateTime.Now;
                // Check for no space in backpack
                Vector2 ValidLocation = FindValidBackpackLocation(true);
                if (ValidLocation.X < 0 || ValidLocation.Y < 0)
                {
                    Log("No more space to pickup a 2-slot item, now running town-run routine.");
                    if (!bLastTownRunCheckResult)
                        bPreStashPauseDone = false;
                    bLastTownRunCheckResult = true;
                    return true;
                }
                // Check durability percentages
                foreach (ACDItem tempitem in ZetaDia.Me.Inventory.Equipped)
                {
                    if (tempitem.BaseAddress != IntPtr.Zero)
                    {
                        if (tempitem.DurabilityPercent <= Zeta.CommonBot.Settings.CharacterSettings.Instance.RepairWhenDurabilityBelow)
                        {
                            Log("Items may need repair, now running town-run routine.");
                            if (!bLastTownRunCheckResult)
                                bPreStashPauseDone = false;
                            bWantToTownRun = true;
                        }
                    }
                }
            }
            bLastTownRunCheckResult = bWantToTownRun;
            return bWantToTownRun;
        }


        // **********************************************************************************************
        // *****  Safety pauses to make sure we aren't still coming through the portal or selling   *****
        // **********************************************************************************************

        private static bool bPreStashPauseDone = false;
        private static double iPreStashLoops = 0;
        private static bool GilesPreStashPauseOverlord(object ret)
        {
            return (!bPreStashPauseDone);
        }
        private static RunStatus GilesStashPrePause(object ret)
        {
            bPreStashPauseDone = true;
            iPreStashLoops = 0;
            return RunStatus.Success;
        }
        private static RunStatus GilesStashPause(object ret)
        {
            iPreStashLoops++;
            if (iPreStashLoops < 30)
                return RunStatus.Running;
            return RunStatus.Success;
        }





        // **********************************************************************************************
        // *****       Stash Overlord values all items and checks if we have anything to stash      *****
        // **********************************************************************************************

        private static bool GilesStashOverlord(object ret)
        {
            hashGilesCachedKeepItems = new HashSet<GilesCachedACDItem>();
            bNeedsEquipmentRepairs = false;
            bGilesForcedVendoring = false;
            bool bShouldVisitStash = false;
            foreach (ACDItem thisitem in ZetaDia.Me.Inventory.Backpack)
            {
                if (thisitem.BaseAddress != IntPtr.Zero)
                {
                    // Find out if this item's in a protected bag slot
                    if (!ItemManager.ItemIsProtected(thisitem))
                    {
                        GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(thisitem.InternalName, thisitem.Name, thisitem.Level, thisitem.ItemQualityLevel, thisitem.Gold, thisitem.GameBalanceId,
                            thisitem.DynamicId, thisitem.Stats.WeaponDamagePerSecond, thisitem.IsOneHand, thisitem.DyeType, thisitem.ItemType, thisitem.FollowerSpecialType,
                            thisitem.IsUnidentified, thisitem.ItemStackQuantity, thisitem.Stats, thisitem);
                        bool bShouldStashThis = settings.bUseGilesFilters ? ShouldWeStashThis(thiscacheditem) : ItemManager.ShouldStashItem(thisitem);
                        if (bShouldStashThis)
                        {
                            hashGilesCachedKeepItems.Add(thiscacheditem);
                            bShouldVisitStash = true;
                        }
                    }
                }
                else
                {
                    Log("GSError: Diablo 3 memory read error, or item became invalid [StashOver-1]", true);
                }
            }
            return bShouldVisitStash;
        }

        // **********************************************************************************************
        // *****                     Pre Stash prepares stuff for our stash run                     *****
        // **********************************************************************************************

        private static RunStatus GilesOptimisedPreStash(object ret)
        {
            if (settings.bDebugInfo)
                BotMain.StatusText = "Town run: Stash routine started";
            Log("GSDebug: Stash routine started.", true);
            bLoggedAnythingThisStash = false;
            bUpdatedStashMap = false;
            iCurrentItemLoops = 0;
            RandomizeTheTimer();
            return RunStatus.Success;
        }

        // **********************************************************************************************
        // *****            Post Stash tidies up and signs off log file after a stash run           *****
        // **********************************************************************************************


        private static RunStatus GilesOptimisedPostStash(object ret)
        {
            Log("GSDebug: Stash routine ending sequence...", true);
            // See if there's any legendary items we should send Prowl notifications about
            while (pushQueue.Count > 0) { SendNotification(pushQueue.Dequeue()); }

            // Lock memory (probably not actually necessary anymore, since we handle all item stuff ourselves!?)
            using (ZetaDia.Memory.AcquireFrame())
            {
                ZetaDia.Actors.Update();
            }
            if (bLoggedAnythingThisStash)
            {
                FileStream LogStream = null;
                try
                {
                    LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - StashLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read);
                    using (StreamWriter LogWriter = new StreamWriter(LogStream))
                        LogWriter.WriteLine("");
                    LogStream.Close();
                    if (settings.bEnableEmail && EmailMessage.Length > 0)
                        SendEmail(sEmailAddress, sEmailAddress, "New DB stash loot - " + sBotName, EmailMessage.ToString(), SmtpServer, sEmailPassword);
                    EmailMessage.Clear();
                }
                catch (IOException)
                {
                    Log("Fatal Error: File access error for signing off the stash log file.");
                    if (LogStream != null)
                        LogStream.Close();
                }
                bLoggedAnythingThisStash = false;
            }
            Log("GSDebug: Stash routine finished.", true);
            return RunStatus.Success;
        }

        // **********************************************************************************************
        // *****                  Lovely smooth one-at-a-time stashing routine                      *****
        // **********************************************************************************************

        private static RunStatus GilesOptimisedStash(object ret)
        {
            ZetaDia.Actors.Update();
            if (ZetaDia.Actors.Me == null)
            {
                Log("GSError: Diablo 3 memory read error, or item became invalid [CoreStash-1]", true);
                return RunStatus.Failure;
            }
            Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
            Vector3 vectorStashLocation = new Vector3(0f, 0f, 0f);
            DiaObject objPlayStash = ZetaDia.Actors.GetActorsOfType<GizmoPlayerSharedStash>(true).FirstOrDefault<GizmoPlayerSharedStash>();
            if (objPlayStash != null)
                vectorStashLocation = objPlayStash.Position;
            else
                switch (ZetaDia.CurrentAct)
                {
                    case Act.A1:
                        vectorStashLocation = new Vector3(2971.285f, 2798.801f, 24.04533f); break;
                    case Act.A2:
                        vectorStashLocation = new Vector3(323.4543f, 228.5806f, 0.1f); break;
                    case Act.A3:
                    case Act.A4:
                        vectorStashLocation = new Vector3(389.3798f, 390.7143f, 0.3321428f); break;
                }
            float iDistanceFromStash = Vector3.Distance(vectorPlayerPosition, vectorStashLocation);
            if (iDistanceFromStash > 120f)
                return RunStatus.Failure;
            if (iDistanceFromStash > 7f)
            {
                ZetaDia.Me.UsePower(SNOPower.Walk, vectorStashLocation, ZetaDia.Me.WorldDynamicId);
                return RunStatus.Running;
            }
            if (objPlayStash == null)
                return RunStatus.Failure;
            if (!UIElements.StashWindow.IsVisible)
            {
                objPlayStash.Interact();
                return RunStatus.Running;
            }

            if (!bUpdatedStashMap)
            {
                // Array for what blocks are or are not blocked
                for (int iRow = 0; iRow <= 29; iRow++)
                    for (int iColumn = 0; iColumn <= 6; iColumn++)
                        GilesStashSlotBlocked[iColumn, iRow] = false;
                // Block off the entire of any "protected stash pages"
                foreach (int iProtPage in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedStashPages)
                    for (int iProtRow = 0; iProtRow <= 9; iProtRow++)
                        for (int iProtColumn = 0; iProtColumn <= 6; iProtColumn++)
                            GilesStashSlotBlocked[iProtColumn, iProtRow + (iProtPage * 10)] = true;
                // Remove rows we don't have
                for (int iRow = (ZetaDia.Me.NumSharedStashSlots / 7); iRow <= 29; iRow++)
                    for (int iColumn = 0; iColumn <= 6; iColumn++)
                        GilesStashSlotBlocked[iColumn, iRow] = true;
                // Map out all the items already in the stash
                foreach (ACDItem tempitem in ZetaDia.Me.Inventory.StashItems)
                {
                    if (tempitem.BaseAddress != IntPtr.Zero)
                    {
                        int inventoryRow = tempitem.InventoryRow;
                        int inventoryColumn = tempitem.InventoryColumn;
                        // Mark this slot as not-free
                        GilesStashSlotBlocked[inventoryColumn, inventoryRow] = true;
                        // Try and reliably find out if this is a two slot item or not
                        GilesItemType tempItemType = DetermineItemType(tempitem.InternalName, tempitem.ItemType, tempitem.FollowerSpecialType);
                        if (DetermineIsTwoSlot(tempItemType) && inventoryRow != 19 && inventoryRow != 9 && inventoryRow != 29)
                        {
                            GilesStashSlotBlocked[inventoryColumn, inventoryRow + 1] = true;
                        }
                        else if (DetermineIsTwoSlot(tempItemType) && (inventoryRow == 19 || inventoryRow == 9 || inventoryRow == 29))
                        {
                            Log("GSError: DemonBuddy thinks this item is 2 slot even though it's at bottom row of a stash page: " + tempitem.Name + " [" + tempitem.InternalName +
                                "] type=" + tempItemType.ToString() + " @ slot " + (inventoryRow + 1).ToString() + "/" +
                                (inventoryColumn + 1).ToString(), true);
                        }
                    }
                } // Loop through all stash items
                bUpdatedStashMap = true;
            } // Need to update the stash map?


            if (hashGilesCachedKeepItems.Count > 0)
            {
                iCurrentItemLoops++;
                if (iCurrentItemLoops < iItemDelayLoopLimit)
                    return RunStatus.Running;
                iCurrentItemLoops = 0;
                RandomizeTheTimer();

                GilesCachedACDItem thisitem = hashGilesCachedKeepItems.FirstOrDefault();
                bool bDidStashSucceed = GilesStashAttempt(thisitem);
                if (!bDidStashSucceed)
                    Log("There was an unknown error stashing an item.", true);
                if (thisitem != null)
                    hashGilesCachedKeepItems.Remove(thisitem);
                if (hashGilesCachedKeepItems.Count > 0)
                    return RunStatus.Running;
            }
            return RunStatus.Success;
        }


        // **********************************************************************************************
        // *****  Sell Overlord - determines if we should visit the vendor for repairs or selling   *****
        // **********************************************************************************************

        private static bool GilesSellOverlord(object ret)
        {
            bGilesForcedVendoring = false;
            hashGilesCachedSellItems = new HashSet<GilesCachedACDItem>();
            bool bShouldVisitVendor = false;
            // Check durability percentages
            iLowestDurabilityFound = -1;
            foreach (ACDItem tempitem in ZetaDia.Me.Inventory.Equipped)
            {
                if (tempitem.BaseAddress != IntPtr.Zero)
                {
                    if (tempitem.DurabilityPercent <= Zeta.CommonBot.Settings.CharacterSettings.Instance.RepairWhenDurabilityBelow)
                    {
                        iLowestDurabilityFound = tempitem.DurabilityPercent;
                        bNeedsEquipmentRepairs = true;
                        bShouldVisitVendor = true;
                    }
                }
            }
            // Check for anything to sell
            foreach (ACDItem thisitem in ZetaDia.Me.Inventory.Backpack)
            {
                if (thisitem.BaseAddress != IntPtr.Zero)
                {
                    if (!ItemManager.ItemIsProtected(thisitem))
                    {
                        GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(thisitem.InternalName, thisitem.Name, thisitem.Level, thisitem.ItemQualityLevel, thisitem.Gold, thisitem.GameBalanceId,
                            thisitem.DynamicId, thisitem.Stats.WeaponDamagePerSecond, thisitem.IsOneHand, thisitem.DyeType, thisitem.ItemType, thisitem.FollowerSpecialType,
                            thisitem.IsUnidentified, thisitem.ItemStackQuantity, thisitem.Stats, thisitem);
                        bool bShouldSellThis = settings.bUseGilesFilters ? GilesSellValidation(thiscacheditem.ThisInternalName, thiscacheditem.ThisLevel, thiscacheditem.ThisQuality, thiscacheditem.ThisDBItemType, thiscacheditem.ThisFollowerType) : ItemManager.ShouldSellItem(thisitem);
                        // Don't sell stuff that we want to salvage, if using custom loot-rules
                        if (!settings.bUseGilesFilters && ItemManager.ShouldSalvageItem(thisitem))
                        {
                            bShouldSellThis = false;
                        }
                        if (bShouldSellThis)
                        {
                            hashGilesCachedSellItems.Add(thiscacheditem);
                            bShouldVisitVendor = true;
                        }
                    }
                }
                else
                {
                    Log("GSError: Diablo 3 memory read error, or item became invalid [SellOver-1]", true);
                }
            }
            return bShouldVisitVendor;
        }

        // **********************************************************************************************
        // *****             Pre Sell sets everything up ready for running to vendor                *****
        // **********************************************************************************************

        private static RunStatus GilesOptimisedPreSell(object ret)
        {
            if (settings.bDebugInfo)
                BotMain.StatusText = "Town run: Sell routine started";
            Log("GSDebug: Sell routine started.", true);
            ZetaDia.Actors.Update();
            if (ZetaDia.Actors.Me == null)
            {
                Log("GSError: Diablo 3 memory read error, or item became invalid [PreSell-1]", true);
                return RunStatus.Failure;
            }
            bGoToSafetyPointFirst = true;
            bGoToSafetyPointSecond = false;
            bLoggedJunkThisStash = false;
            bCurrentlyMoving = false;
            bReachedDestination = false;
            iCurrentItemLoops = 0;
            RandomizeTheTimer();
            return RunStatus.Success;
        }

        // **********************************************************************************************
        // *****    Sell Routine replacement for smooth one-at-a-time item selling and handling     *****
        // **********************************************************************************************

        private static RunStatus GilesOptimisedSell(object ret)
        {
            string sVendorName = "";
            switch (ZetaDia.CurrentAct)
            {
                case Act.A1:
                    sVendorName = "a1_uniquevendor_miner"; break;
                case Act.A2:
                    sVendorName = "a2_uniquevendor_peddler"; break;
                case Act.A3:
                    sVendorName = "a3_uniquevendor_collector"; break;
                case Act.A4:
                    sVendorName = "a4_uniquevendor_collector"; break;
            }
            if (bGoToSafetyPointFirst)
            {
                Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
                Vector3 vectorSellLocation = new Vector3(0f, 0f, 0f);
                switch (ZetaDia.CurrentAct)
                {
                    case Act.A1:
                        vectorSellLocation = new Vector3(2941.904f, 2812.825f, 24.04533f); break;
                    case Act.A2:
                        vectorSellLocation = new Vector3(295.2101f, 265.1436f, 0.1000002f); break;
                    case Act.A3:
                    case Act.A4:
                        vectorSellLocation = new Vector3(410.6073f, 355.8762f, 0.1000005f); break;
                }
                float iDistanceFromSell = Vector3.Distance(vectorPlayerPosition, vectorSellLocation);
                if (bCurrentlyMoving)
                {
                    if (iDistanceFromSell <= 8f)
                    {
                        bGoToSafetyPointFirst = false;
                        /*if (ZetaDia.CurrentAct == Act.A2)
                            bGoToSafetyPointSecond = true;*/
                        bCurrentlyMoving = false;
                    }
                    else if (iLastDistance == iDistanceFromSell)
                    {
                        ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId);
                    }
                    return RunStatus.Running;
                }
                iLastDistance = iDistanceFromSell;

                if (iDistanceFromSell > 120f)
                    return RunStatus.Failure;
                if (iDistanceFromSell > 8f)
                {
                    ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId);
                    bCurrentlyMoving = true;
                    return RunStatus.Running;
                }
                bCurrentlyMoving = false;
                bGoToSafetyPointFirst = false;
                /*if (ZetaDia.CurrentAct == Act.A2)
                    bGoToSafetyPointSecond = true;*/
                return RunStatus.Running;
            }
            /*if (bGoToSafetyPointSecond)
            {
                Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
                Vector3 vectorSellLocation = new Vector3(0f, 0f, 0f);
                switch (ZetaDia.CurrentAct)
                {
                    case Act.A1:
                        vectorSellLocation = new Vector3(2941.904f, 2812.825f, 24.04533f); break;
                    case Act.A2:
                        vectorSellLocation = new Vector3(295.0274f, 156.2243f, -1.834799f); break;
                    case Act.A3:
                    case Act.A4:
                        vectorSellLocation = new Vector3(410.6073f, 355.8762f, 0.1000005f); break;
                }
                float iDistanceFromSell = Vector3.Distance(vectorPlayerPosition, vectorSellLocation);

                if (bCurrentlyMoving)
                {
                    if (iDistanceFromSell <= 8f)
                    {
                        bGoToSafetyPointSecond = false;
                        bCurrentlyMoving = false;
                    }
                    else if (iLastDistance == iDistanceFromSell)
                    {
                        try
                        {
                            ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId);
                        }
                        catch
                        {
                            Log("GSError: Diablo 3 move command error [CoreSell-1: " + thisGilesDiaItem.ThisInternalName + "]", true);
                        }
                    }
                    return RunStatus.Running;
                }
                iLastDistance = iDistanceFromSell;

                if (iDistanceFromSell > 120f)
                    return RunStatus.Failure;
                if (iDistanceFromSell > 8f)
                {
                    ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId);
                    bCurrentlyMoving = true;
                    return RunStatus.Running;
                }
                bCurrentlyMoving = false;
                bGoToSafetyPointSecond = false;
                return RunStatus.Running;
            }*/
            if (!bReachedDestination)
            {
                Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
                Vector3 vectorSellLocation = new Vector3(0f, 0f, 0f);
                switch (ZetaDia.CurrentAct)
                {
                    case Act.A1:
                        vectorSellLocation = new Vector3(2896.159f, 2779.443f, 24.04532f); break;
                    case Act.A2:
                        vectorSellLocation = new Vector3(286.0302f, 280.2442f, 0.1000038f); break;
                    case Act.A3:
                    case Act.A4:
                        vectorSellLocation = new Vector3(447.8373f, 324.1446f, 0.1000005f); break;
                }

                DiaUnit objSellNavigation = ZetaDia.Actors.GetActorsOfType<DiaUnit>(true).FirstOrDefault<DiaUnit>(u => u.Name.ToLower().StartsWith(sVendorName));
                if (objSellNavigation != null)
                    vectorSellLocation = objSellNavigation.Position;
                float iDistanceFromSell = Vector3.Distance(vectorPlayerPosition, vectorSellLocation);

                if (bCurrentlyMoving)
                {
                    if (iDistanceFromSell <= 9.5f)
                    {
                        bReachedDestination = true;
                        if (objSellNavigation == null)
                            return RunStatus.Failure;
                        objSellNavigation.Interact();
                    }
                    else if (iLastDistance == iDistanceFromSell)
                    {
                        ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId);
                    }
                    return RunStatus.Running;
                }
                iLastDistance = iDistanceFromSell;

                if (iDistanceFromSell > 120f)
                    return RunStatus.Failure;
                if (iDistanceFromSell > 9.5f)
                {
                    bCurrentlyMoving = true;
                    ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId);
                    return RunStatus.Running;
                }
                bReachedDestination = true;
                if (objSellNavigation == null)
                    return RunStatus.Failure;
                objSellNavigation.Interact();
            }

            if (!Zeta.Internals.UIElement.IsValidElement(12123456831356216535L) || !Zeta.Internals.UIElement.FromHash(12123456831356216535L).IsVisible)
            {
                DiaUnit objSellNavigation = ZetaDia.Actors.GetActorsOfType<DiaUnit>(true).FirstOrDefault<DiaUnit>(u => u.Name.ToLower().StartsWith(sVendorName));
                if (objSellNavigation == null)
                    return RunStatus.Failure;
                objSellNavigation.Interact();
                return RunStatus.Running;
            }


            if (hashGilesCachedSellItems.Count > 0)
            {
                iCurrentItemLoops++;
                if (iCurrentItemLoops < iItemDelayLoopLimit)
                    return RunStatus.Running;
                iCurrentItemLoops = 0;
                RandomizeTheTimer();

                GilesCachedACDItem thisitem = hashGilesCachedSellItems.FirstOrDefault();
                // Item log for cool stuff sold
                if (thisitem != null)
                {
                    GilesItemType OriginalGilesItemType = DetermineItemType(thisitem.ThisInternalName, thisitem.ThisDBItemType, thisitem.ThisFollowerType);
                    GilesBaseItemType thisGilesBaseType = DetermineBaseType(OriginalGilesItemType);
                    if (thisGilesBaseType == GilesBaseItemType.WeaponTwoHand || thisGilesBaseType == GilesBaseItemType.WeaponOneHand || thisGilesBaseType == GilesBaseItemType.WeaponRange ||
                        thisGilesBaseType == GilesBaseItemType.Armor || thisGilesBaseType == GilesBaseItemType.Jewelry || thisGilesBaseType == GilesBaseItemType.Offhand ||
                        thisGilesBaseType == GilesBaseItemType.FollowerItem)
                    {
                        double iThisItemValue = ValueThisItem(thisitem, OriginalGilesItemType);
                        LogJunkItems(thisitem, thisGilesBaseType, OriginalGilesItemType, iThisItemValue);
                    }
                    ZetaDia.Me.Inventory.SellItem(thisitem.ThisDynamicID);
                }
                if (thisitem != null)
                    hashGilesCachedSellItems.Remove(thisitem);
                if (hashGilesCachedSellItems.Count > 0)
                    return RunStatus.Running;
            }
            bCurrentlyMoving = false;
            bReachedSafety = false;
            return RunStatus.Success;
        }

        // **********************************************************************************************
        // *****        Post Sell tidies everything up and signs off junk log after selling         *****
        // **********************************************************************************************

        private static RunStatus GilesOptimisedPostSell(object ret)
        {
            Log("GSDebug: Sell routine ending sequence...", true);
            using (ZetaDia.Memory.AcquireFrame())
            {
                ZetaDia.Actors.Update();
            }
            // Always repair, but only if we have enough money
            if (bNeedsEquipmentRepairs && iLowestDurabilityFound < 20 && iLowestDurabilityFound > -1 && ZetaDia.Me.Inventory.Coinage < 40000)
            {
                Log("***************************");
                Log("Emergency Stop: You need repairs but don't have enough money. Stopping the bot to prevent infinite death loop.");
                BotMain.Stop();
            }
            ZetaDia.Me.Inventory.RepairEquippedItems();
            bNeedsEquipmentRepairs = false;
            if (bLoggedJunkThisStash)
            {
                FileStream LogStream = null;
                try
                {
                    LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - JunkLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read);
                    using (StreamWriter LogWriter = new StreamWriter(LogStream))
                        LogWriter.WriteLine("");
                    LogStream.Close();
                }
                catch (IOException)
                {
                    Log("Fatal Error: File access error for signing off the junk log file.");
                    if (LogStream != null)
                        LogStream.Close();
                }
                bLoggedJunkThisStash = false;
            }

            // See if we can close the inventory window
            if (Zeta.Internals.UIElement.IsValidElement(0x368FF8C552241695))
            {
                try
                {
                    var el = Zeta.Internals.UIElement.FromHash(0x368FF8C552241695);
                    if (el != null && el.IsValid && el.IsVisible && el.IsEnabled)
                        el.Click();
                }
                catch
                {
                    // Do nothing if it fails, just catching to prevent any big errors/plugin crashes from this
                }
            }


            /*if (!bReachedSafety && ZetaDia.CurrentAct == Act.A2)
            {
                Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
                Vector3 vectorSafeLocation = new Vector3(284.3047f, 212.2945f, 0.1f);
                float iDistanceFromSafety = Vector3.Distance(vectorPlayerPosition, vectorSafeLocation);
                if (bCurrentlyMoving)
                {
                    if (iDistanceFromSafety <= 8f)
                    {
                        bReachedSafety = true;
                        bCurrentlyMoving = false;
                    }
                    else if (iLastDistance == iDistanceFromSafety)
                    {
                        try
                        {
                            ZetaDia.Me.UsePower(SNOPower.Walk, vectorSafeLocation, ZetaDia.Me.WorldDynamicId);
                        }
                        catch
                        {
                            Log("GSError: Diablo 3 move command error [PostSell-1: " + thisGilesDiaItem.ThisInternalName + "]", true);
                        }
                    }
                    return RunStatus.Running;
                }
                iLastDistance = iDistanceFromSafety;

                if (iDistanceFromSafety > 120f)
                    return RunStatus.Failure;
                if (iDistanceFromSafety > 8f)
                {
                    ZetaDia.Me.UsePower(SNOPower.Walk, vectorSafeLocation, ZetaDia.Me.WorldDynamicId);
                    bCurrentlyMoving = true;
                    return RunStatus.Running;
                }
                bCurrentlyMoving = false;
                bReachedSafety = true;
            }*/

            Log("GSDebug: Sell routine finished.", true);
            return RunStatus.Success;
        }




        // **********************************************************************************************
        // *****         Salvage Overlord determines if we should visit the blacksmith or not       *****
        // **********************************************************************************************

        private static bool GilesSalvageOverlord(object ret)
        {
            bGilesForcedVendoring = false;
            hashGilesCachedSalvageItems = new HashSet<GilesCachedACDItem>();
            bool bShouldVisitSmith = false;

            // Check for anything to salvage
            foreach (ACDItem thisitem in ZetaDia.Me.Inventory.Backpack)
            {
                if (thisitem.BaseAddress != IntPtr.Zero)
                {
                    if (!ItemManager.ItemIsProtected(thisitem))
                    {
                        GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(thisitem.InternalName, thisitem.Name, thisitem.Level, thisitem.ItemQualityLevel, thisitem.Gold, thisitem.GameBalanceId,
                            thisitem.DynamicId, thisitem.Stats.WeaponDamagePerSecond, thisitem.IsOneHand, thisitem.DyeType, thisitem.ItemType, thisitem.FollowerSpecialType,
                            thisitem.IsUnidentified, thisitem.ItemStackQuantity, thisitem.Stats, thisitem);
                        bool bShouldSalvageThis = settings.bUseGilesFilters ? GilesSalvageValidation(thiscacheditem.ThisInternalName, thiscacheditem.ThisLevel, thiscacheditem.ThisQuality, thiscacheditem.ThisDBItemType, thiscacheditem.ThisFollowerType) : ItemManager.ShouldSalvageItem(thisitem);
                        if (bShouldSalvageThis)
                        {
                            hashGilesCachedSalvageItems.Add(thiscacheditem);
                            bShouldVisitSmith = true;
                        }
                    }
                }
                else
                {
                    Log("GSError: Diablo 3 memory read error, or item became invalid [SalvageOver-1]", true);
                }
            }
            return bShouldVisitSmith;
        }

        // **********************************************************************************************
        // *****             Pre Salvage sets everything up ready for our blacksmith run            *****
        // **********************************************************************************************

        private static RunStatus GilesOptimisedPreSalvage(object ret)
        {
            if (settings.bDebugInfo)
                BotMain.StatusText = "Town run: Salvage routine started";
            Log("GSDebug: Salvage routine started.", true);
            ZetaDia.Actors.Update();
            if (ZetaDia.Actors.Me == null)
            {
                Log("GSError: Diablo 3 memory read error, or item became invalid [PreSalvage-1]", true);
                return RunStatus.Failure;
            }
            bGoToSafetyPointFirst = true;
            bGoToSafetyPointSecond = false;
            bLoggedJunkThisStash = false;
            bCurrentlyMoving = false;
            bReachedDestination = false;
            iCurrentItemLoops = 0;
            RandomizeTheTimer();
            return RunStatus.Success;
        }

        // **********************************************************************************************
        // *****                 Nice smooth one-at-a-time salvaging replacement                    *****
        // **********************************************************************************************

        private static RunStatus GilesOptimisedSalvage(object ret)
        {
            if (bGoToSafetyPointFirst)
            {
                Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
                Vector3 vectorSalvageLocation = new Vector3(0f, 0f, 0f);
                switch (ZetaDia.CurrentAct)
                {
                    case Act.A1:
                        vectorSalvageLocation = new Vector3(2949.626f, 2815.065f, 24.04389f); break;
                    case Act.A2:
                        vectorSalvageLocation = new Vector3(289.6358f, 232.1146f, 0.1f); break;
                    case Act.A3:
                    case Act.A4:
                        vectorSalvageLocation = new Vector3(379.6096f, 415.6198f, 0.3321424f); break;
                }
                float iDistanceFromSalvage = Vector3.Distance(vectorPlayerPosition, vectorSalvageLocation);

                if (bCurrentlyMoving)
                {
                    if (iDistanceFromSalvage <= 8f)
                    {
                        bGoToSafetyPointFirst = false;
                        if (ZetaDia.CurrentAct == Act.A3)
                            bGoToSafetyPointSecond = true;
                        bCurrentlyMoving = false;
                    }
                    else if (iLastDistance == iDistanceFromSalvage)
                    {
                        ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId);
                    }
                    return RunStatus.Running;
                }
                iLastDistance = iDistanceFromSalvage;

                if (iDistanceFromSalvage > 120f)
                    return RunStatus.Failure;
                if (iDistanceFromSalvage > 8f)
                {
                    ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId);
                    bCurrentlyMoving = true;
                    return RunStatus.Running;
                }
                bCurrentlyMoving = false;
                bGoToSafetyPointFirst = false;
                if (ZetaDia.CurrentAct == Act.A3)
                    bGoToSafetyPointSecond = true;
                return RunStatus.Running;
            }
            if (bGoToSafetyPointSecond)
            {
                Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
                Vector3 vectorSalvageLocation = new Vector3(0f, 0f, 0f);
                switch (ZetaDia.CurrentAct)
                {
                    case Act.A1:
                        vectorSalvageLocation = new Vector3(2949.626f, 2815.065f, 24.04389f); break;
                    case Act.A2:
                        vectorSalvageLocation = new Vector3(289.6358f, 232.1146f, 0.1f); break;
                    case Act.A3:
                    case Act.A4:
                        vectorSalvageLocation = new Vector3(328.6024f, 425.4113f, 0.2758033f); break;
                }
                float iDistanceFromSalvage = Vector3.Distance(vectorPlayerPosition, vectorSalvageLocation);

                if (bCurrentlyMoving)
                {
                    if (iDistanceFromSalvage <= 8f)
                    {
                        bGoToSafetyPointSecond = false;
                        bCurrentlyMoving = false;
                    }
                    else if (iLastDistance == iDistanceFromSalvage)
                    {
                        ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId);
                    }
                    return RunStatus.Running;
                }
                iLastDistance = iDistanceFromSalvage;

                if (iDistanceFromSalvage > 120f)
                    return RunStatus.Failure;
                if (iDistanceFromSalvage > 8f)
                {
                    ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId);
                    bCurrentlyMoving = true;
                    return RunStatus.Running;
                }
                bCurrentlyMoving = false;
                bGoToSafetyPointSecond = false;
                return RunStatus.Running;
            }
            if (!bReachedDestination)
            {
                Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
                Vector3 vectorSalvageLocation = new Vector3(0f, 0f, 0f);
                DiaUnit objSalvageNavigation = ZetaDia.Actors.GetActorsOfType<DiaUnit>(true).FirstOrDefault<DiaUnit>(u => u.IsSalvageShortcut);
                if (objSalvageNavigation != null)
                    vectorSalvageLocation = objSalvageNavigation.Position;
                else
                    switch (ZetaDia.CurrentAct)
                    {
                        case Act.A1:
                            vectorSalvageLocation = new Vector3(2942.137f, 2854.078f, 24.04533f); break;
                        case Act.A2:
                            vectorSalvageLocation = new Vector3(275.6705f, 221.1727f, 0.1f); break;
                        case Act.A3:
                        case Act.A4:
                            vectorSalvageLocation = new Vector3(328.6024f, 425.4113f, 0.2758033f); break;
                    }
                float iDistanceFromSalvage = Vector3.Distance(vectorPlayerPosition, vectorSalvageLocation);

                if (bCurrentlyMoving)
                {
                    if (iDistanceFromSalvage <= 9.5f)
                    {
                        bReachedDestination = true;
                        if (objSalvageNavigation == null)
                            return RunStatus.Failure;
                        objSalvageNavigation.Interact();
                    }
                    else if (iLastDistance == iDistanceFromSalvage)
                    {
                        ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId);
                    }
                    return RunStatus.Running;
                }
                iLastDistance = iDistanceFromSalvage;

                if (iDistanceFromSalvage > 120f)
                    return RunStatus.Failure;
                if (iDistanceFromSalvage > 9.5f)
                {
                    bCurrentlyMoving = true;
                    ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId);
                    return RunStatus.Running;
                }
                bReachedDestination = true;
                if (objSalvageNavigation == null)
                    return RunStatus.Failure;
                objSalvageNavigation.Interact();
            }

            if (!Zeta.Internals.UIElement.IsValidElement(0x359867fd497d2ff3L) || !Zeta.Internals.UIElement.FromHash(0x359867fd497d2ff3L).IsVisible)
            {
                DiaUnit objSalvageNavigation = ZetaDia.Actors.GetActorsOfType<DiaUnit>(true).FirstOrDefault<DiaUnit>(u => u.IsSalvageShortcut);
                if (objSalvageNavigation == null)
                    return RunStatus.Failure;
                objSalvageNavigation.Interact();
                return RunStatus.Running;
            }

            if (hashGilesCachedSalvageItems.Count > 0)
            {
                iCurrentItemLoops++;
                if (iCurrentItemLoops < iItemDelayLoopLimit)
                    return RunStatus.Running;
                iCurrentItemLoops = 0;
                RandomizeTheTimer();

                GilesCachedACDItem thisitem = hashGilesCachedSalvageItems.FirstOrDefault();
                if (thisitem != null)
                {
                    // Item log for cool stuff stashed
                    GilesItemType OriginalGilesItemType = DetermineItemType(thisitem.ThisInternalName, thisitem.ThisDBItemType, thisitem.ThisFollowerType);
                    GilesBaseItemType thisGilesBaseType = DetermineBaseType(OriginalGilesItemType);
                    if (thisGilesBaseType == GilesBaseItemType.WeaponTwoHand || thisGilesBaseType == GilesBaseItemType.WeaponOneHand || thisGilesBaseType == GilesBaseItemType.WeaponRange ||
                        thisGilesBaseType == GilesBaseItemType.Armor || thisGilesBaseType == GilesBaseItemType.Jewelry || thisGilesBaseType == GilesBaseItemType.Offhand ||
                        thisGilesBaseType == GilesBaseItemType.FollowerItem)
                    {
                        double iThisItemValue = ValueThisItem(thisitem, OriginalGilesItemType);
                        LogJunkItems(thisitem, thisGilesBaseType, OriginalGilesItemType, iThisItemValue);
                    }
                    ZetaDia.Me.Inventory.SalvageItem(thisitem.ThisDynamicID);
                }
                if (thisitem != null)
                    hashGilesCachedSalvageItems.Remove(thisitem);
                if (hashGilesCachedSalvageItems.Count > 0)
                    return RunStatus.Running;
            }
            bReachedSafety = false;
            bCurrentlyMoving = false;
            return RunStatus.Success;
        }

        // **********************************************************************************************
        // *****         Post salvage cleans up and signs off junk log file after salvaging         *****
        // **********************************************************************************************

        private static RunStatus GilesOptimisedPostSalvage(object ret)
        {
            Log("GSDebug: Salvage routine ending sequence...", true);
            using (ZetaDia.Memory.AcquireFrame())
            {
                ZetaDia.Actors.Update();
            }
            if (bLoggedJunkThisStash)
            {
                FileStream LogStream = null;
                try
                {
                    LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - JunkLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read);
                    using (StreamWriter LogWriter = new StreamWriter(LogStream))
                        LogWriter.WriteLine("");
                    LogStream.Close();
                }
                catch (IOException)
                {
                    Log("Fatal Error: File access error for signing off the junk log file.");
                    if (LogStream != null)
                        LogStream.Close();
                }
                bLoggedJunkThisStash = false;
            }


            if (!bReachedSafety && ZetaDia.CurrentAct == Act.A3)
            {
                Vector3 vectorPlayerPosition = ZetaDia.Me.Position;
                Vector3 vectorSafeLocation = new Vector3(379.6096f, 415.6198f, 0.3321424f);
                float iDistanceFromSafety = Vector3.Distance(vectorPlayerPosition, vectorSafeLocation);
                if (bCurrentlyMoving)
                {
                    if (iDistanceFromSafety <= 8f)
                    {
                        bGoToSafetyPointSecond = false;
                        bCurrentlyMoving = false;
                    }
                    else if (iLastDistance == iDistanceFromSafety)
                    {
                        ZetaDia.Me.UsePower(SNOPower.Walk, vectorSafeLocation, ZetaDia.Me.WorldDynamicId);
                    }
                    return RunStatus.Running;
                }
                iLastDistance = iDistanceFromSafety;

                if (iDistanceFromSafety > 120f)
                    return RunStatus.Failure;
                if (iDistanceFromSafety > 8f)
                {
                    ZetaDia.Me.UsePower(SNOPower.Walk, vectorSafeLocation, ZetaDia.Me.WorldDynamicId);
                    bCurrentlyMoving = true;
                    return RunStatus.Running;
                }
                bCurrentlyMoving = false;
                bReachedSafety = true;
            }

            Log("GSDebug: Salvage routine finished.", true);
            return RunStatus.Success;
        }

        // **********************************************************************************************
        // *****              Arrange your stash by highest to lowest scoring items                 *****
        // **********************************************************************************************

        public class GilesStashSort
        {
            public double dStashScore { get; set; }
            public int iStashOrPack { get; set; }
            public int iInventoryColumn { get; set; }
            public int iInventoryRow { get; set; }
            public int iDynamicID { get; set; }
            public bool bIsTwoSlot { get; set; }

            public GilesStashSort(double stashscore, int stashorpack, int icolumn, int irow, int dynamicid, bool twoslot)
            {
                dStashScore = stashscore;
                iStashOrPack = stashorpack;
                iInventoryColumn = icolumn;
                iInventoryRow = irow;
                iDynamicID = dynamicid;
                bIsTwoSlot = twoslot;
            }
        }


        // **********************************************************************************************
        // *****          Search backpack to see if we have room for a 2-slot item anywhere         *****
        // **********************************************************************************************
        private static bool[,] GilesBackpackSlotBlocked = new bool[10, 6];
        private static Vector2 SortingFindLocationBackpack(bool bOriginalTwoSlot)
        {
            int iPointX = -1;
            int iPointY = -1;
            for (int iRow = 0; iRow <= 5; iRow++)
            {
                for (int iColumn = 0; iColumn <= 9; iColumn++)
                {
                    if (!GilesBackpackSlotBlocked[iColumn, iRow])
                    {
                        bool bNotEnoughSpace = false;
                        if (iRow < 5)
                        {
                            bNotEnoughSpace = (bOriginalTwoSlot && GilesBackpackSlotBlocked[iColumn, iRow + 1]);
                        }
                        else
                        {
                            if (bOriginalTwoSlot)
                                bNotEnoughSpace = true;
                        }
                        if (!bNotEnoughSpace)
                        {
                            iPointX = iColumn;
                            iPointY = iRow;
                            goto FoundPackLocation;
                        }
                    }
                }
            }
        FoundPackLocation:
            if ((iPointX < 0) || (iPointY < 0))
            {
                return new Vector2(-1, -1);
            }
            return new Vector2(iPointX, iPointY);
        }
        private static Vector2 SortingFindLocationStash(bool bOriginalTwoSlot, bool bEndOfStash = false)
        {
            int iPointX = -1;
            int iPointY = -1;
            for (int iRow = 0; iRow <= 29; iRow++)
            {
                for (int iColumn = 0; iColumn <= 6; iColumn++)
                {
                    if (!GilesStashSlotBlocked[iColumn, iRow])
                    {
                        bool bNotEnoughSpace = false;
                        if (iRow != 9 && iRow != 19 && iRow != 29)
                        {
                            bNotEnoughSpace = (bOriginalTwoSlot && GilesStashSlotBlocked[iColumn, iRow + 1]);
                        }
                        else
                        {
                            if (bOriginalTwoSlot)
                                bNotEnoughSpace = true;
                        }
                        if (!bNotEnoughSpace)
                        {
                            iPointX = iColumn;
                            iPointY = iRow;
                            if (!bEndOfStash)
                                goto FoundStashLocation;
                        }
                    }
                }
            }
        FoundStashLocation:
            if ((iPointX < 0) || (iPointY < 0))
            {
                return new Vector2(-1, -1);
            }
            return new Vector2(iPointX, iPointY);
        }

        private static void SortStash()
        {
            // Try and update the player-data
            ZetaDia.Actors.Update();
            // Check we can get the player dynamic ID
            int iPlayerDynamicID = -1;
            try
            {
                iPlayerDynamicID = ZetaDia.Me.CommonData.DynamicId;
            }
            catch
            {
                Log("Failure getting your player data from DemonBuddy, abandoning the sort!");
                return;
            }
            if (iPlayerDynamicID == -1)
            {
                Log("Failure getting your player data, abandoning the sort!");
                return;
            }

            // List used for all the sorting
            List<GilesStashSort> listSortMyStash = new List<GilesStashSort>();

            // Map out the backpack free slots
            for (int iRow = 0; iRow <= 5; iRow++)
                for (int iColumn = 0; iColumn <= 9; iColumn++)
                    GilesBackpackSlotBlocked[iColumn, iRow] = false;
            foreach (ACDItem tempitem in ZetaDia.Me.Inventory.Backpack)
            {
                int inventoryRow = tempitem.InventoryRow;
                int inventoryColumn = tempitem.InventoryColumn;
                // Mark this slot as not-free
                GilesBackpackSlotBlocked[inventoryColumn, inventoryRow] = true;
                // Try and reliably find out if this is a two slot item or not
                GilesItemType tempItemType = DetermineItemType(tempitem.InternalName, tempitem.ItemType, tempitem.FollowerSpecialType);
                if (DetermineIsTwoSlot(tempItemType) && inventoryRow < 5)
                {
                    GilesBackpackSlotBlocked[inventoryColumn, inventoryRow + 1] = true;
                }
            }

            // Map out the stash free slots
            for (int iRow = 0; iRow <= 29; iRow++)
                for (int iColumn = 0; iColumn <= 6; iColumn++)
                    GilesStashSlotBlocked[iColumn, iRow] = false;
            // Block off the entire of any "protected stash pages"
            foreach (int iProtPage in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedStashPages)
                for (int iProtRow = 0; iProtRow <= 9; iProtRow++)
                    for (int iProtColumn = 0; iProtColumn <= 6; iProtColumn++)
                        GilesStashSlotBlocked[iProtColumn, iProtRow + (iProtPage * 10)] = true;
            // Remove rows we don't have
            for (int iRow = (ZetaDia.Me.NumSharedStashSlots / 7); iRow <= 29; iRow++)
                for (int iColumn = 0; iColumn <= 6; iColumn++)
                    GilesStashSlotBlocked[iColumn, iRow] = true;

            // Map out all the items already in the stash and store their scores if appropriate
            foreach (ACDItem thisitem in ZetaDia.Me.Inventory.StashItems)
            {
                int inventoryRow = thisitem.InventoryRow;
                int inventoryColumn = thisitem.InventoryColumn;
                // Mark this slot as not-free
                GilesStashSlotBlocked[inventoryColumn, inventoryRow] = true;
                // Try and reliably find out if this is a two slot item or not
                GilesItemType tempItemType = DetermineItemType(thisitem.InternalName, thisitem.ItemType, thisitem.FollowerSpecialType);
                bool bIsTwoSlot = DetermineIsTwoSlot(tempItemType);
                if (bIsTwoSlot && inventoryRow != 19 && inventoryRow != 9 && inventoryRow != 29)
                {
                    GilesStashSlotBlocked[inventoryColumn, inventoryRow + 1] = true;
                }
                else if (bIsTwoSlot && (inventoryRow == 19 || inventoryRow == 9 || inventoryRow == 29))
                {
                    Log("WARNING: There was an error reading your stash, abandoning the process.");
                    Log("Always make sure you empty your backpack, open the stash, then RESTART DEMONBUDDY before sorting!");
                    return;
                }
                GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(thisitem.InternalName, thisitem.Name, thisitem.Level, thisitem.ItemQualityLevel, thisitem.Gold, thisitem.GameBalanceId,
                    thisitem.DynamicId, thisitem.Stats.WeaponDamagePerSecond, thisitem.IsOneHand, thisitem.DyeType, thisitem.ItemType, thisitem.FollowerSpecialType,
                    thisitem.IsUnidentified, thisitem.ItemStackQuantity, thisitem.Stats, thisitem);
                double iThisItemValue = ValueThisItem(thiscacheditem, tempItemType);
                double iNeedScore = ScoreNeeded(tempItemType);
                // Ignore stackable items
                if (!DetermineIsStackable(tempItemType) && tempItemType != GilesItemType.StaffOfHerding)
                {
                    listSortMyStash.Add(new GilesStashSort(((iThisItemValue / iNeedScore) * 1000), 1, inventoryColumn, inventoryRow, thisitem.DynamicId, bIsTwoSlot));
                }
            } // Loop through all stash items

            // Sort the items in the stash by their row number, lowest to highest
            listSortMyStash.Sort((p1, p2) => p1.iInventoryRow.CompareTo(p2.iInventoryRow));

            // Now move items into your backpack until full, then into the END of the stash
            Vector2 vFreeSlot;
            foreach (GilesStashSort thisstashsort in listSortMyStash)
            {
                vFreeSlot = SortingFindLocationBackpack(thisstashsort.bIsTwoSlot);
                int iStashOrPack = 1;
                if (vFreeSlot.X == -1 || vFreeSlot.Y == -1)
                {
                    vFreeSlot = SortingFindLocationStash(thisstashsort.bIsTwoSlot, true);
                    if (vFreeSlot.X == -1 || vFreeSlot.Y == -1)
                        continue;
                    iStashOrPack = 2;
                }
                if (iStashOrPack == 1)
                {
                    ZetaDia.Me.Inventory.MoveItem(thisstashsort.iDynamicID, iPlayerDynamicID, InventorySlot.PlayerBackpack, (int)vFreeSlot.X, (int)vFreeSlot.Y);
                    GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow] = false;
                    if (thisstashsort.bIsTwoSlot)
                        GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow + 1] = false;
                    GilesBackpackSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y] = true;
                    if (thisstashsort.bIsTwoSlot)
                        GilesBackpackSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y + 1] = true;
                    thisstashsort.iInventoryColumn = (int)vFreeSlot.X;
                    thisstashsort.iInventoryRow = (int)vFreeSlot.Y;
                    thisstashsort.iStashOrPack = 2;
                }
                else
                {
                    ZetaDia.Me.Inventory.MoveItem(thisstashsort.iDynamicID, iPlayerDynamicID, InventorySlot.PlayerSharedStash, (int)vFreeSlot.X, (int)vFreeSlot.Y);
                    GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow] = false;
                    if (thisstashsort.bIsTwoSlot)
                        GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow + 1] = false;
                    GilesStashSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y] = true;
                    if (thisstashsort.bIsTwoSlot)
                        GilesStashSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y + 1] = true;
                    thisstashsort.iInventoryColumn = (int)vFreeSlot.X;
                    thisstashsort.iInventoryRow = (int)vFreeSlot.Y;
                    thisstashsort.iStashOrPack = 1;
                }
                Thread.Sleep(150);
            }

            // Now sort the items by their score, highest to lowest
            listSortMyStash.Sort((p1, p2) => p1.dStashScore.CompareTo(p2.dStashScore));
            listSortMyStash.Reverse();

            // Now fill the stash in ordered-order
            foreach (GilesStashSort thisstashsort in listSortMyStash)
            {
                vFreeSlot = SortingFindLocationStash(thisstashsort.bIsTwoSlot, false);
                if (vFreeSlot.X == -1 || vFreeSlot.Y == -1)
                {
                    Log("Failure trying to put things back into stash, no stash slots free? Abandoning...");
                    return;
                }
                ZetaDia.Me.Inventory.MoveItem(thisstashsort.iDynamicID, iPlayerDynamicID, InventorySlot.PlayerSharedStash, (int)vFreeSlot.X, (int)vFreeSlot.Y);
                if (thisstashsort.iStashOrPack == 1)
                {
                    GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow] = false;
                    if (thisstashsort.bIsTwoSlot)
                        GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow + 1] = false;
                }
                else
                {
                    GilesBackpackSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow] = false;
                    if (thisstashsort.bIsTwoSlot)
                        GilesBackpackSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow + 1] = false;
                }
                GilesStashSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y] = true;
                if (thisstashsort.bIsTwoSlot)
                    GilesStashSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y + 1] = true;
                thisstashsort.iStashOrPack = 1;
                thisstashsort.iInventoryRow = (int)vFreeSlot.Y;
                thisstashsort.iInventoryColumn = (int)vFreeSlot.X;
                Thread.Sleep(150);
            }

            Log("Stash sorted!");
        }

        // **********************************************************************************************
        // *****                 Output test scores for everything in the backpack                  *****
        // **********************************************************************************************

        private static void TestScoring()
        {
            if (bTestingBackpack) return;
            bTestingBackpack = true;
            ZetaDia.Actors.Update();
            if (ZetaDia.Actors.Me == null)
            {
                Logging.Write("Error testing scores - not in game world?");
                return;
            }
            if (ZetaDia.IsInGame && !ZetaDia.IsLoadingWorld)
            {
                bOutputItemScores = true;
                Logging.Write("===== Outputting Test Scores =====");
                foreach (ACDItem item in ZetaDia.Actors.Me.Inventory.Backpack)
                {
                    if (item.BaseAddress == IntPtr.Zero)
                    {
                        Logging.Write("GSError: Diablo 3 memory read error, or item became invalid [TestScore-1]");
                    }
                    else
                    {
                        GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(item.InternalName, item.Name, item.Level, item.ItemQualityLevel, item.Gold, item.GameBalanceId, item.DynamicId,
                            item.Stats.WeaponDamagePerSecond, item.IsOneHand, item.DyeType, item.ItemType, item.FollowerSpecialType, item.IsUnidentified, item.ItemStackQuantity,
                            item.Stats, item);
                        bool bShouldStashTest = ShouldWeStashThis(thiscacheditem);
                        Logging.Write(bShouldStashTest ? "***** KEEP *****" : "-- TRASH --");
                    }
                }
                Logging.Write("===== Finished Test Score Outputs =====");
                Logging.Write("Note: See bad scores? Wrong item types? Known DB bug - restart DB before using the test button!");
                bOutputItemScores = false;
            }
            else
            {
                Logging.Write("Error testing scores - not in game world?");
            }
            bTestingBackpack = false;
        }


        // **********************************************************************************************
        // *****      Determine if we should stash this item or not based on item type and score    *****
        // **********************************************************************************************

        private static bool ShouldWeStashThis(GilesCachedACDItem thisitem)
        {
            // Stash all unidentified items - assume we want to keep them since we are using an identifier over-ride
            if (thisitem.IsUnidentified)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] = (autokeep unidentified items)");
                return true;
            }
            // Now look for Misc items we might want to keep
            GilesItemType TrueItemType = DetermineItemType(thisitem.ThisInternalName, thisitem.ThisDBItemType, thisitem.ThisFollowerType);

            if (TrueItemType == GilesItemType.StaffOfHerding)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep staff of herding)");
                return true;
            }
            if (TrueItemType == GilesItemType.CraftingMaterial)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep craft materials)");
                return true;
            }
            if (TrueItemType == GilesItemType.CraftingPlan)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep plans)");
                return true;
            }
            if (TrueItemType == GilesItemType.Emerald)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep gems)");
                return true;
            }
            if (TrueItemType == GilesItemType.Amethyst)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep gems)");
                return true;
            }
            if (TrueItemType == GilesItemType.Topaz)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep gems)");
                return true;
            }
            if (TrueItemType == GilesItemType.Ruby)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep gems)");
                return true;
            }
            if (TrueItemType == GilesItemType.CraftTome)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep tomes)");
                return true;
            }
            if (TrueItemType == GilesItemType.InfernalKey)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep infernal key)");
                return true;
            }
            if (TrueItemType == GilesItemType.HealthPotion)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (ignoring potions)");
                return false;
            }

            // let the item rule set decide about this item
            Interpreter.InterpreterAction action = interpreter.checkItem(thisitem.ACDItem, false);
            switch (action)
            {
                case Interpreter.InterpreterAction.KEEP:
                    Log("ItemRuleSet: " + thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] = (KEEP)");
                    return true;
                case Interpreter.InterpreterAction.TRASH:
                    Log("ItemRuleSet: " + thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] = (TRASH)");
                    return false;
                default:
                    break;
            }

            if (thisitem.ThisQuality >= ItemQuality.Legendary)
            {
                if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep legendaries)");
                return true;
            }

            // Ok now try to do some decent item scoring based on item types
            double iNeedScore = ScoreNeeded(TrueItemType);
            double iMyScore = ValueThisItem(thisitem, TrueItemType);
            if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = " + iMyScore.ToString());
            if (iMyScore >= iNeedScore) return true;

            // If we reached this point, then we found no reason to keep the item!
            return false;
        }

        // **********************************************************************************************
        // *****             Return the score needed to keep something by the item type             *****
        // **********************************************************************************************

        private static double ScoreNeeded(GilesItemType thisGilesItemType)
        {
            double iThisNeedScore = 0;
            // Weapons
            if (thisGilesItemType == GilesItemType.Axe || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Dagger ||
                thisGilesItemType == GilesItemType.FistWeapon || thisGilesItemType == GilesItemType.Mace || thisGilesItemType == GilesItemType.MightyWeapon ||
                thisGilesItemType == GilesItemType.Spear || thisGilesItemType == GilesItemType.Sword || thisGilesItemType == GilesItemType.Wand ||
                thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.TwoHandCrossbow || thisGilesItemType == GilesItemType.TwoHandMace ||
                thisGilesItemType == GilesItemType.TwoHandMighty || thisGilesItemType == GilesItemType.TwoHandPolearm || thisGilesItemType == GilesItemType.TwoHandStaff ||
                thisGilesItemType == GilesItemType.TwoHandSword || thisGilesItemType == GilesItemType.TwoHandAxe || thisGilesItemType == GilesItemType.HandCrossbow || thisGilesItemType == GilesItemType.TwoHandBow)
                iThisNeedScore = settings.iNeedPointsToKeepWeapon;
            // Jewelry
            if (thisGilesItemType == GilesItemType.Ring || thisGilesItemType == GilesItemType.Amulet || thisGilesItemType == GilesItemType.FollowerEnchantress ||
                thisGilesItemType == GilesItemType.FollowerScoundrel || thisGilesItemType == GilesItemType.FollowerTemplar)
                iThisNeedScore = settings.iNeedPointsToKeepJewelry;

            // Armor
            if (thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source || thisGilesItemType == GilesItemType.Quiver ||
                thisGilesItemType == GilesItemType.Shield || thisGilesItemType == GilesItemType.Belt || thisGilesItemType == GilesItemType.Boots ||
                thisGilesItemType == GilesItemType.Bracers || thisGilesItemType == GilesItemType.Chest || thisGilesItemType == GilesItemType.Cloak ||
                thisGilesItemType == GilesItemType.Gloves || thisGilesItemType == GilesItemType.Helm || thisGilesItemType == GilesItemType.Pants ||
                thisGilesItemType == GilesItemType.MightyBelt || thisGilesItemType == GilesItemType.Shoulders || thisGilesItemType == GilesItemType.SpiritStone ||
                thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.WizardHat)
                iThisNeedScore = settings.iNeedPointsToKeepArmor;
            return Math.Round(iThisNeedScore);
        }

        // **********************************************************************************************
        // *****             The bizarre mystery function to score your lovely items!               *****
        // **********************************************************************************************

        private static double ValueThisItem(GilesCachedACDItem thisitem, GilesItemType thisGilesItemType)
        {
            double iTotalPoints = 0;
            bool bAbandonShip = true;
            double[] iThisItemsMaxStats = new double[TOTALSTATS];
            double[] iThisItemsMaxPoints = new double[TOTALSTATS];

            sValueItemStatString = "";
            sJunkItemStatString = "";

            GilesBaseItemType thisGilesBaseType = DetermineBaseType(thisGilesItemType);

            #region FillMaxStats
            // One Handed Weapons 
            if (thisGilesItemType == GilesItemType.Axe || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Dagger ||
                 thisGilesItemType == GilesItemType.FistWeapon || thisGilesItemType == GilesItemType.Mace || thisGilesItemType == GilesItemType.MightyWeapon ||
                 thisGilesItemType == GilesItemType.Spear || thisGilesItemType == GilesItemType.Sword || thisGilesItemType == GilesItemType.Wand)
            {
                Array.Copy(iMaxWeaponOneHand, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iWeaponPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Two Handed Weapons
            if (thisGilesItemType == GilesItemType.TwoHandAxe || thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.TwoHandMace ||
                thisGilesItemType == GilesItemType.TwoHandMighty || thisGilesItemType == GilesItemType.TwoHandPolearm || thisGilesItemType == GilesItemType.TwoHandStaff ||
                thisGilesItemType == GilesItemType.TwoHandSword)
            {
                Array.Copy(iMaxWeaponTwoHand, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iWeaponPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Ranged Weapons
            if (thisGilesItemType == GilesItemType.TwoHandCrossbow || thisGilesItemType == GilesItemType.TwoHandBow || thisGilesItemType == GilesItemType.HandCrossbow)
            {
                Array.Copy(iMaxWeaponRanged, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iWeaponPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                if (thisGilesItemType == GilesItemType.HandCrossbow)
                {
                    iThisItemsMaxStats[TOTALDPS] -= 150;
                }
                bAbandonShip = false;
            }
            // Off-handed stuff
            // Mojo, Source, Quiver
            if (thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source || thisGilesItemType == GilesItemType.Quiver)
            {
                Array.Copy(iMaxOffHand, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Shields
            if (thisGilesItemType == GilesItemType.Shield)
            {
                Array.Copy(iMaxShield, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Jewelry
            // Ring
            if (thisGilesItemType == GilesItemType.Amulet)
            {
                Array.Copy(iMaxAmulet, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iJewelryPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Ring
            if (thisGilesItemType == GilesItemType.Ring)
            {
                Array.Copy(iMaxRing, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iJewelryPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Armor
            // Belt
            if (thisGilesItemType == GilesItemType.Belt)
            {
                Array.Copy(iMaxBelt, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Boots
            if (thisGilesItemType == GilesItemType.Boots)
            {
                Array.Copy(iMaxBoots, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Bracers
            if (thisGilesItemType == GilesItemType.Bracers)
            {
                Array.Copy(iMaxBracer, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Chest
            if (thisGilesItemType == GilesItemType.Chest)
            {
                Array.Copy(iMaxChest, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            if (thisGilesItemType == GilesItemType.Cloak)
            {
                Array.Copy(iMaxCloak, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Gloves
            if (thisGilesItemType == GilesItemType.Gloves)
            {
                Array.Copy(iMaxGloves, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Helm
            if (thisGilesItemType == GilesItemType.Helm)
            {
                Array.Copy(iMaxHelm, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Pants
            if (thisGilesItemType == GilesItemType.Pants)
            {
                Array.Copy(iMaxPants, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            if (thisGilesItemType == GilesItemType.MightyBelt)
            {
                Array.Copy(iMaxMightyBelt, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Shoulders
            if (thisGilesItemType == GilesItemType.Shoulders)
            {
                Array.Copy(iMaxShoulders, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            if (thisGilesItemType == GilesItemType.SpiritStone)
            {
                Array.Copy(iMaxSpiritStone, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            if (thisGilesItemType == GilesItemType.VoodooMask)
            {
                Array.Copy(iMaxVoodooMask, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Wizard Hat
            if (thisGilesItemType == GilesItemType.WizardHat)
            {
                Array.Copy(iMaxWizardHat, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            // Follower Items
            if (thisGilesItemType == GilesItemType.FollowerEnchantress || thisGilesItemType == GilesItemType.FollowerScoundrel || thisGilesItemType == GilesItemType.FollowerTemplar)
            {
                Array.Copy(iMaxFollower, iThisItemsMaxStats, TOTALSTATS);
                Array.Copy(iJewelryPointsAtMax, iThisItemsMaxPoints, TOTALSTATS);
                bAbandonShip = false;
            }
            #endregion

            // Double safety check for unidentified items
            if (thisitem.IsUnidentified) bAbandonShip = true;

            // Make sure we got a valid item here, otherwise score it a big fat 0
            if (bAbandonShip)
            {
                if (bFullAnalysis) Log("-- Invalid Item Type or Unidentified?");
                return 0;
            }

            if (bFullAnalysis) Log("NEXT ITEM= " + thisitem.ThisRealName + " - " + thisitem.ThisInternalName + " [" + thisGilesBaseType.ToString() + " - " + thisGilesItemType.ToString() + "]");

            #region CustomScoring
            if ((thisGilesItemType == GilesItemType.Quiver || 
                    thisGilesItemType == GilesItemType.SpiritStone || 
                    thisGilesItemType == GilesItemType.Source || 
                    thisGilesItemType == GilesItemType.Shield || 
                    thisGilesItemType == GilesItemType.Mojo) 
                && thisitem.CritPercent <= 0)
            {
                //Logging.Write(string.Format("ItemType: {0}, has no crit so returning a score of 1000", thisGilesItemType));
                return 1000;
            }
            #endregion

            int[] PossiblePrimarys = new int[] { System.Convert.ToInt32(thisitem.Dexterity), System.Convert.ToInt32(thisitem.Strength), System.Convert.ToInt32(thisitem.Intelligence) };
            int PrimaryStat = PossiblePrimarys.Max();
            int itemScore = 1000;

           // Logging.Write("===== Type: =====: " + thisGilesBaseType);
            //Logging.Write("===== ItemType: =====: " + thisGilesItemType);
            //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);

            //Calc Primary Stat
            #region CalcPrimaryStat
            if (PrimaryStat > 0)
            {
                int maxStat = 0;
                if (System.Convert.ToInt32(thisitem.Dexterity) > System.Convert.ToInt32(thisitem.Strength) &&
                    System.Convert.ToInt32(thisitem.Dexterity) > System.Convert.ToInt32(thisitem.Intelligence))
                {
                    maxStat = DEXTERITY;
                    sValueItemStatString = "Dex:" + thisitem.Dexterity.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (System.Convert.ToInt32(thisitem.Intelligence) > System.Convert.ToInt32(thisitem.Strength) &&
                    System.Convert.ToInt32(thisitem.Intelligence) > System.Convert.ToInt32(thisitem.Dexterity))
                {
                    maxStat = INTELLIGENCE;
                    sValueItemStatString = "Int:" + thisitem.Intelligence.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (System.Convert.ToInt32(thisitem.Strength) > System.Convert.ToInt32(thisitem.Dexterity) &&
                    System.Convert.ToInt32(thisitem.Strength) > System.Convert.ToInt32(thisitem.Intelligence))
                {
                    maxStat = STRENGTH;
                    sValueItemStatString = "Str:" + thisitem.Strength.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                //Override primary stat
                if (thisGilesItemType == GilesItemType.Quiver ||
                    thisGilesItemType == GilesItemType.SpiritStone)
                {
                    PrimaryStat = System.Convert.ToInt32(thisitem.Dexterity);
                    maxStat = System.Convert.ToInt32(DEXTERITY);
                    sValueItemStatString = "Dex: " + thisitem.Dexterity.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisGilesItemType == GilesItemType.Source ||
                   thisGilesItemType == GilesItemType.CeremonialKnife ||
                   thisGilesItemType == GilesItemType.WizardHat ||
                   thisGilesItemType == GilesItemType.VoodooMask)
                {
                    PrimaryStat = System.Convert.ToInt32(thisitem.Intelligence);
                    maxStat = INTELLIGENCE;
                    sValueItemStatString = "Int: " + thisitem.Intelligence.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisGilesItemType == GilesItemType.MightyBelt ||
                    thisGilesItemType == GilesItemType.MightyWeapon)
                {
                    PrimaryStat = System.Convert.ToInt32(thisitem.Strength);
                    maxStat = STRENGTH;
                    sValueItemStatString = "Str: " + thisitem.Strength.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                //Logging.Write("===== Item's Primary Stat =====: " + PrimaryStat);
                //Logging.Write("===== Item's Max Primary Stat =====: " + iThisItemsMaxStats[maxStat]);
                //Logging.Write("===== Item's Primary Stat Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(PrimaryStat / iThisItemsMaxStats[maxStat]) * 10000));
                itemScore += System.Convert.ToInt32(System.Convert.ToDouble(PrimaryStat / iThisItemsMaxStats[maxStat]) * 10000);
            }
            #endregion

            #region FollowerItems
            if (thisGilesItemType == GilesItemType.FollowerEnchantress && iThisItemsMaxStats[INTELLIGENCE] > 0)
            {
                itemScore = System.Convert.ToInt32(System.Convert.ToDouble(thisitem.Intelligence / iThisItemsMaxStats[INTELLIGENCE]) * 26000);
                sValueItemStatString += "Int: " + thisitem.Intelligence.ToString();
                //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
            }

            if (thisGilesItemType == GilesItemType.FollowerScoundrel && iThisItemsMaxStats[DEXTERITY] > 0)
            {
                itemScore = System.Convert.ToInt32(System.Convert.ToDouble(thisitem.Dexterity / iThisItemsMaxStats[DEXTERITY]) * 26000);
                sValueItemStatString += "Dex: " + thisitem.Dexterity.ToString();
                //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
            }

            if (thisGilesItemType == GilesItemType.FollowerTemplar && iThisItemsMaxStats[STRENGTH] > 0)
            {
                itemScore = System.Convert.ToInt32(System.Convert.ToDouble(thisitem.Strength / iThisItemsMaxStats[STRENGTH]) * 26000);
                sValueItemStatString += "Str:" + thisitem.Strength.ToString();
                //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
            }
            #endregion

            #region WeaponScoring
            if ((thisGilesBaseType == GilesBaseItemType.WeaponTwoHand ||
               thisGilesBaseType == GilesBaseItemType.WeaponOneHand ||
               thisGilesBaseType == GilesBaseItemType.WeaponRange) &&
               thisitem.WeaponDamagePerSecond > 0)
            {
                sValueItemStatString += " -DPS:" + thisitem.WeaponDamagePerSecond.ToString();
                //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
            }
            //Scoring Weapon
            if ((thisGilesBaseType == GilesBaseItemType.WeaponTwoHand ||
                 thisGilesBaseType == GilesBaseItemType.WeaponOneHand ||
                 thisGilesBaseType == GilesBaseItemType.WeaponRange) &&
                (thisitem.WeaponDamagePerSecond > 0 &&
                ((thisitem.WeaponDamagePerSecond / iThisItemsMaxStats[TOTALDPS]) * 100) > 85))
            {
                if (thisitem.Sockets >= 1)
                {
                    //Logging.Write("===== Item's Number of SOCKETS =====: " + thisitem.Sockets);
                    //Logging.Write("===== Item's SOCKETS points =====: " + thisitem.Sockets * 3000);
                    itemScore += System.Convert.ToInt32(thisitem.Sockets * 3000);
                    sValueItemStatString += " -Sockets:" + thisitem.Sockets.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.CritDamagePercent > 0 && iThisItemsMaxStats[CRITDAMAGE] > 0)
                {
                    //Logging.Write("===== Item's CRITDAMAGE =====: " + thisitem.CritDamagePercent);
                    //Logging.Write("===== Item's Max CRITDAMAGE =====: " + iThisItemsMaxStats[CRITDAMAGE]);
                    //Logging.Write("===== Item's CRITDAMAGE Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.CritDamagePercent / iThisItemsMaxStats[CRITDAMAGE]) * 5000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.CritDamagePercent / iThisItemsMaxStats[CRITDAMAGE]) * 5000);
                    sValueItemStatString += " -CritDamagePercent:" + thisitem.CritDamagePercent.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.LifeOnHit > 0 && iThisItemsMaxStats[LIFEONHIT] > 0)
                {
                    //Logging.Write("===== Item's LIFEONHIT =====: " + thisitem.LifeOnHit);
                    //Logging.Write("===== Item's Max LIFEONHIT =====: " + iThisItemsMaxStats[LIFEONHIT]);
                    //Logging.Write("===== Item's LIFEONHIT Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.LifeOnHit / iThisItemsMaxStats[LIFEONHIT]) * 5000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.LifeOnHit / iThisItemsMaxStats[LIFEONHIT]) * 5000);
                    sValueItemStatString += " -LifeOnHit:" + thisitem.LifeOnHit.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.LifeSteal > 0 && iThisItemsMaxStats[LIFESTEAL] > 0)
                {
                    //Logging.Write("===== Item's LIFESTEAL =====: " + thisitem.LifeSteal);
                    //Logging.Write("===== Item's Max LIFESTEAL =====: " + iThisItemsMaxStats[LIFESTEAL]);
                    //Logging.Write("===== Item's LIFESTEAL Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.LifeSteal / iThisItemsMaxStats[LIFESTEAL]) * 5000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.LifeSteal / iThisItemsMaxStats[LIFESTEAL]) * 5000);
                    sValueItemStatString += " -LifeSteal:" + thisitem.LifeSteal.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                //Logging.Write("===== Item's DPS =====: " + thisitem.WeaponDamagePerSecond);
                //Logging.Write("===== Item's Max DPS =====: " + iThisItemsMaxStats[TOTALDPS]);
                //Logging.Write("===== Item's DPS Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.WeaponDamagePerSecond / iThisItemsMaxStats[TOTALDPS]) * 25000));
                itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.WeaponDamagePerSecond / iThisItemsMaxStats[TOTALDPS]) * 25000);
            }
            #endregion

            #region ArmorJewelry
            //Scoring Armor + Jewelry
            if (thisGilesBaseType == GilesBaseItemType.Jewelry ||
                thisGilesBaseType == GilesBaseItemType.Armor ||
                thisGilesBaseType == GilesBaseItemType.Misc ||
                thisGilesBaseType == GilesBaseItemType.Offhand ||
                thisGilesBaseType == GilesBaseItemType.Unknown)
            {
                if (thisGilesItemType == GilesItemType.Source)
                {
                    if (thisitem.MinDamage > 0 && iThisItemsMaxStats[MINDAMAGE] > 0)
                    {
                        //Logging.Write("===== Item's MINDAMAGE =====: " + thisitem.MinDamage);
                        //Logging.Write("===== Item's Max MINDAMAGE =====: " + iThisItemsMaxStats[MINDAMAGE]);
                        //Logging.Write("===== Item's MINDAMAGE Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.MinDamage / iThisItemsMaxStats[MINDAMAGE]) * 5000));
                        itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.MinDamage / iThisItemsMaxStats[MINDAMAGE]) * 5000);
                        sValueItemStatString += " -MinDamage:" + thisitem.MinDamage.ToString();
                        //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                    }

                    if (thisitem.MaxDamage > 0 && iThisItemsMaxStats[MAXDAMAGE] > 0)
                    {
                        //Logging.Write("===== Item's MAXDAMAGE =====: " + thisitem.MaxDamage);
                        //Logging.Write("===== Item's Max MAXDAMAGE =====: " + iThisItemsMaxStats[MAXDAMAGE]);
                        //Logging.Write("===== Item's MAXDAMAGE Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.MaxDamage / iThisItemsMaxStats[MAXDAMAGE]) * 5000));
                        itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.MaxDamage / iThisItemsMaxStats[MAXDAMAGE]) * 5000);
                        sValueItemStatString += " -MaxDamage:" + thisitem.MaxDamage.ToString();
                        //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                    }
                }

                if (thisitem.CritPercent > 0 && iThisItemsMaxStats[CRITCHANCE] > 0)
                {
                    //itemScore += 7500;
                    //Logging.Write("===== Item's CRITCHANCE =====: " + thisitem.CritPercent);
                    //Logging.Write("===== Item's Max CRITCHANCE =====: " + iThisItemsMaxStats[CRITCHANCE]);
                    //Logging.Write("===== Item's CRITCHANCE Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.CritPercent / iThisItemsMaxStats[CRITCHANCE]) * 10000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.CritPercent / iThisItemsMaxStats[CRITCHANCE]) * 10000);
                    sValueItemStatString += " -CritPercent:" + thisitem.CritPercent.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.CritDamagePercent > 0 && iThisItemsMaxStats[CRITDAMAGE] > 0)
                {
                    //Logging.Write("===== Item's CRITDAMAGE =====: " + thisitem.CritDamagePercent);
                    //Logging.Write("===== Item's Max CRITDAMAGE =====: " + iThisItemsMaxStats[CRITDAMAGE]);
                    //Logging.Write("===== Item's CRITDAMAGE Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.CritDamagePercent / iThisItemsMaxStats[CRITDAMAGE]) * 10000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.CritDamagePercent / iThisItemsMaxStats[CRITDAMAGE]) * 10000);
                    sValueItemStatString += " -CritDamagePercent:" + thisitem.CritDamagePercent.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.AttackSpeedPercent > 0 && iThisItemsMaxStats[ATTACKSPEED] > 0)
                {
                    //itemScore += 7500;
                    //Logging.Write("===== Item's ATTACKSPEED =====: " + thisitem.AttackSpeedPercent);
                    //Logging.Write("===== Item's Max ATTACKSPEED =====: " + iThisItemsMaxStats[ATTACKSPEED]);
                    //Logging.Write("===== Item's ATTACKSPEED Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.AttackSpeedPercent / iThisItemsMaxStats[ATTACKSPEED]) * 10000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.AttackSpeedPercent / iThisItemsMaxStats[ATTACKSPEED]) * 10000);
                    sValueItemStatString += " -AttackSpeedPercent:" + thisitem.AttackSpeedPercent.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                //Logging.Write("At VITALITY");
                if (thisitem.Vitality > 0 && iThisItemsMaxStats[VITALITY] > 0)
                {
                    //Logging.Write("===== Item's VITALITY =====: " + thisitem.Vitality);
                    //Logging.Write("===== Item's Max VITALITY =====: " + iThisItemsMaxStats[VITALITY]);
                    //Logging.Write("===== Item's VITALITY Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.Vitality / iThisItemsMaxStats[VITALITY]) * 10000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.Vitality / iThisItemsMaxStats[VITALITY]) * 10000);
                    sValueItemStatString += " -Vit:" + thisitem.Vitality.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                //Logging.Write("At ALLRESIST");
                if (thisitem.ResistAll > 0 && iThisItemsMaxStats[ALLRESIST] > 0)
                {
                    //Logging.Write("===== Item's ALLRESIST =====: " + thisitem.ResistAll);
                    //Logging.Write("===== Item's Max ALLRESIST =====: " + iThisItemsMaxStats[ALLRESIST]);
                    //Logging.Write("===== Item's ALLRESIST Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistAll / iThisItemsMaxStats[ALLRESIST]) * 10000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistAll / iThisItemsMaxStats[ALLRESIST]) * 10000);
                    sValueItemStatString += " -RA:" + thisitem.ResistAll.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                //Logging.Write("At MOVEMENTSPEED");
                if (thisitem.MovementSpeed > 0 && iThisItemsMaxStats[MOVEMENTSPEED] > 0)
                {
                    //Logging.Write("===== Item's MOVEMENTSPEED =====: " + thisitem.MovementSpeed);
                    //Logging.Write("===== Item's Max MOVEMENTSPEED =====: " + iThisItemsMaxStats[MOVEMENTSPEED]);
                    //Logging.Write("===== Item's MOVEMENTSPEED Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.MovementSpeed / iThisItemsMaxStats[MOVEMENTSPEED]) * 5000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.MovementSpeed / iThisItemsMaxStats[MOVEMENTSPEED]) * 5000);
                    sValueItemStatString += " -MS:" + thisitem.MovementSpeed.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                //Logging.Write("At PICKUPRADIUS");
                if (thisitem.PickUpRadius > 0 && iThisItemsMaxStats[PICKUPRADIUS] > 0)
                {
                    //Logging.Write("===== Item's PICKUPRADIUS =====: " + thisitem.PickUpRadius);
                    //Logging.Write("===== Item's Max PICKUPRADIUS =====: " + iThisItemsMaxStats[PICKUPRADIUS]);
                    //Logging.Write("===== Item's PICKUPRADIUS Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.PickUpRadius / iThisItemsMaxStats[PICKUPRADIUS]) * 5000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.PickUpRadius / iThisItemsMaxStats[PICKUPRADIUS]) * 5000);
                    sValueItemStatString += " -PR:" + thisitem.PickUpRadius.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.LifePercent > 0 && iThisItemsMaxStats[LIFEPERCENT] > 0)
                {
                   // Logging.Write("===== Item's LIFEPERCENT =====: " + thisitem.LifePercent);
                   // Logging.Write("===== Item's Max LIFEPERCENT =====: " + iThisItemsMaxStats[LIFEPERCENT]);
                    //Logging.Write("===== Item's LIFEPERCENT Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.LifePercent / iThisItemsMaxStats[LIFEPERCENT]) * 5000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.LifePercent / iThisItemsMaxStats[LIFEPERCENT]) * 5000);
                    sValueItemStatString += " -LP:" + thisitem.LifePercent.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.ResistArcane > 0 && iThisItemsMaxStats[RANDOMRESIST] > 0)
                {
                   // Logging.Write("===== Item's ResistArcane =====: " + thisitem.ResistArcane);
                   // Logging.Write("===== Item's Max RANDOMRESIST =====: " + iThisItemsMaxStats[RANDOMRESIST]);
                   // Logging.Write("===== Item's ResistArcane Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistArcane / iThisItemsMaxStats[RANDOMRESIST]) * 4000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistArcane / iThisItemsMaxStats[RANDOMRESIST]) * 4000);
                    sValueItemStatString += " -AR:" + thisitem.ResistArcane.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.ResistCold > 0 && iThisItemsMaxStats[RANDOMRESIST] > 0)
                {
                   // Logging.Write("===== Item's ResistCold =====: " + thisitem.ResistCold);
                   // Logging.Write("===== Item's Max RANDOMRESIST =====: " + iThisItemsMaxStats[RANDOMRESIST]);
                   // Logging.Write("===== Item's ResistCold Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistCold / iThisItemsMaxStats[RANDOMRESIST]) * 2000));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistCold / iThisItemsMaxStats[RANDOMRESIST]) * 2000);
                    sValueItemStatString += " -CR:" + thisitem.ResistCold.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.ResistFire > 0 && iThisItemsMaxStats[RANDOMRESIST] > 0)
                {
                   // Logging.Write("===== Item's ResistFire =====: " + thisitem.ResistFire);
                   // Logging.Write("===== Item's Max RANDOMRESIST =====: " + iThisItemsMaxStats[RANDOMRESIST]);
                   // Logging.Write("===== Item's ResistFire Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistFire / iThisItemsMaxStats[RANDOMRESIST]) * 2500));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistFire / iThisItemsMaxStats[RANDOMRESIST]) * 2500);
                    sValueItemStatString += " -FR:" + thisitem.ResistFire.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.ResistHoly > 0 && iThisItemsMaxStats[RANDOMRESIST] > 0)
                {
                   // Logging.Write("===== Item's ResistHoly =====: " + thisitem.ResistHoly);
                   // Logging.Write("===== Item's Max RANDOMRESIST =====: " + iThisItemsMaxStats[RANDOMRESIST]);
                   // Logging.Write("===== Item's ResistHoly Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistHoly / iThisItemsMaxStats[RANDOMRESIST]) * 1500));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistHoly / iThisItemsMaxStats[RANDOMRESIST]) * 1500);
                    sValueItemStatString += " -HR:" + thisitem.ResistHoly.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.ResistLightning > 0 && iThisItemsMaxStats[RANDOMRESIST] > 0)
                {
                   // Logging.Write("===== Item's ResistLightning =====: " + thisitem.ResistLightning);
                   // Logging.Write("===== Item's Max RANDOMRESIST =====: " + iThisItemsMaxStats[RANDOMRESIST]);
                   // Logging.Write("===== Item's ResistLightning Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistLightning / iThisItemsMaxStats[RANDOMRESIST]) * 1500));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistLightning / iThisItemsMaxStats[RANDOMRESIST]) * 1500);
                    sValueItemStatString += " -LR:" + thisitem.ResistLightning.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.ResistPhysical > 0 && iThisItemsMaxStats[RANDOMRESIST] > 0)
                {
                   // Logging.Write("===== Item's ResistPhysical =====: " + thisitem.ResistPhysical);
                   // Logging.Write("===== Item's Max RANDOMRESIST =====: " + iThisItemsMaxStats[RANDOMRESIST]);
                   // Logging.Write("===== Item's ResistPhysical Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistPhysical / iThisItemsMaxStats[RANDOMRESIST]) * 3500));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistPhysical / iThisItemsMaxStats[RANDOMRESIST]) * 3500);
                    sValueItemStatString += " -PhR:" + thisitem.ResistPhysical.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.ResistPoison > 0 && iThisItemsMaxStats[RANDOMRESIST] > 0)
                {
                   // Logging.Write("===== Item's ResistPoison =====: " + thisitem.ResistPoison);
                   // Logging.Write("===== Item's Max RANDOMRESIST =====: " + iThisItemsMaxStats[RANDOMRESIST]);
                   // Logging.Write("===== Item's ResistPoison Points =====: " + System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistPoison / iThisItemsMaxStats[RANDOMRESIST]) * 3500));
                    itemScore += System.Convert.ToInt32(System.Convert.ToDouble(thisitem.ResistPoison / iThisItemsMaxStats[RANDOMRESIST]) * 3500);
                    sValueItemStatString += " -PoR:" + thisitem.ResistPoison.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }

                if (thisitem.Sockets >= 1)
                {
                   // Logging.Write("===== Item's Number of SOCKETS =====: " + thisitem.Sockets);
                   // Logging.Write("===== Item's SOCKETS points =====: " + thisitem.Sockets * 2500);
                    itemScore += System.Convert.ToInt32(thisitem.Sockets * 2500);
                    sValueItemStatString += " -Sockets:" + thisitem.Sockets.ToString();
                    //Logging.Write("===== sValueItemStatString: =====: " + sValueItemStatString);
                }
            }
            #endregion

            #region FillMaxStats
            //Give Amulets and Rings Bonus 5000 points
            if (thisGilesItemType == GilesItemType.Amulet ||
                    thisGilesItemType == GilesItemType.Ring)
            {
                itemScore += 5000;
               // Logging.Write(string.Format("Adding Bonus 5000 for Amulets and Rings, New item score", itemScore));
            }
            #endregion

            sJunkItemStatString = sValueItemStatString;

            //Logging.Write("===== Final Score =====: " + itemScore.ToString());
            if (bFullAnalysis) Log("TOTAL: " + itemScore.ToString());
            return itemScore;
        }

        public static void SendEmail(string toAddressStr, string fromAddressStr, string subject, string body, string smtpClient, string fromPassword)
        {
            try
            {
                var fromAddress = new MailAddress(fromAddressStr);
                var toAddress = new MailAddress(toAddressStr);

                var smtp = new SmtpClient
                {
                    Host = smtpClient,
                    Port = 587,
                    EnableSsl = true,
                    DeliveryMethod = SmtpDeliveryMethod.Network,
                    UseDefaultCredentials = false,
                    Credentials = new NetworkCredential(fromAddress.Address, fromPassword)
                };
                using (var message = new MailMessage(fromAddress, toAddress)
                {
                    Subject = subject,
                    Body = body
                })
                {
                    smtp.Send(message);
                }
            }
            catch (Exception e)
            {
                Log("Error sending email." + Environment.NewLine + e.ToString());
            }
        }

        public static bool EvaluateItemScoreForNotification(GilesBaseItemType thisgilesbaseitemtype, double ithisitemvalue)
        {
            switch (thisgilesbaseitemtype)
            {
                case GilesBaseItemType.WeaponOneHand:
                case GilesBaseItemType.WeaponRange:
                case GilesBaseItemType.WeaponTwoHand:
                    if (ithisitemvalue >= settings.iNeedPointsToNotifyWeapon)
                        return true;
                    break;
                case GilesBaseItemType.Armor:
                case GilesBaseItemType.Offhand:
                    if (ithisitemvalue >= settings.iNeedPointsToNotifyArmor)
                        return true;
                    break;
                case GilesBaseItemType.Jewelry:
                    if (ithisitemvalue >= settings.iNeedPointsToNotifyJewelry)
                        return true;
                    break;
            }
            return false;
        }

        // 
        // **********************************************************************************************
        // *****                      Log the nice items we found and stashed                       *****
        // **********************************************************************************************

        public static void LogGoodItems(GilesCachedACDItem thisgooditem, GilesBaseItemType thisgilesbaseitemtype, GilesItemType thisgilesitemtype, double ithisitemvalue)
        {
            FileStream LogStream = null;
            try
            {
                LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - StashLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read);
                using (StreamWriter LogWriter = new StreamWriter(LogStream))
                {
                    if (!bLoggedAnythingThisStash)
                    {
                        bLoggedAnythingThisStash = true;
                        LogWriter.WriteLine(DateTime.Now.ToString() + ":");
                        LogWriter.WriteLine("====================");
                    }
                    string sLegendaryString = "";
                    bool bShouldNotify = false;
                    if (thisgooditem.ThisQuality >= ItemQuality.Legendary)
                    {
                        if (!settings.bEnableLegendaryNotifyScore)
                            bShouldNotify = true;
                        else if (settings.bEnableLegendaryNotifyScore && EvaluateItemScoreForNotification(thisgilesbaseitemtype, ithisitemvalue))
                            bShouldNotify = true;
                        if (bShouldNotify)
                            AddNotificationToQueue(thisgooditem.ThisRealName + " [" + thisgilesitemtype.ToString() +
                                "] (Score=" + ithisitemvalue.ToString() + ". " + sValueItemStatString + ")",
                                ZetaDia.Service.CurrentHero.Name + " new legendary!", ProwlNotificationPriority.Emergency);
                        sLegendaryString = " {legendary item}";
                        // Change made by bombastic
                        Logging.Write("+=+=+=+=+=+=+=+=+ LEGENDARY FOUND +=+=+=+=+=+=+=+=+");
                        Logging.Write("+  Name:       " + thisgooditem.ThisRealName + " (" + thisgilesitemtype.ToString() + ")");
                        Logging.Write("+  Score:       " + Math.Round(ithisitemvalue).ToString());
                        Logging.Write("+  Attributes: " + sValueItemStatString);
                        Logging.Write("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+");
                    }
                    else
                    {
                        // Check for non-legendary notifications
                        bShouldNotify = EvaluateItemScoreForNotification(thisgilesbaseitemtype, ithisitemvalue);
                        if (bShouldNotify)
                            AddNotificationToQueue(thisgooditem.ThisRealName + " [" + thisgilesitemtype.ToString() + "] (Score=" + ithisitemvalue.ToString() + ". " + sValueItemStatString + ")", ZetaDia.Service.CurrentHero.Name + " new item!", ProwlNotificationPriority.Emergency);
                    }
                    if (bShouldNotify)
                    {
                        EmailMessage.AppendLine(thisgilesbaseitemtype.ToString() + " - " + thisgilesitemtype.ToString() + " '" + thisgooditem.ThisRealName + "'. Score = " + Math.Round(ithisitemvalue).ToString() + sLegendaryString)
                            .AppendLine("  " + sValueItemStatString)
                            .AppendLine();
                    }
                    LogWriter.WriteLine(thisgilesbaseitemtype.ToString() + " - " + thisgilesitemtype.ToString() + " '" + thisgooditem.ThisRealName + "'. Score = " + Math.Round(ithisitemvalue).ToString() + sLegendaryString);
                    LogWriter.WriteLine("  " + sValueItemStatString);
                    LogWriter.WriteLine("");
                }
                LogStream.Close();
            }
            catch (IOException)
            {
                Log("Fatal Error: File access error for stash log file.");
                if (LogStream != null)
                    LogStream.Close();
            }
        }

        // **********************************************************************************************
        // *****                   Log the rubbish junk items we salvaged or sold                   *****
        // **********************************************************************************************

        public static void LogJunkItems(GilesCachedACDItem thisgooditem, GilesBaseItemType thisgilesbaseitemtype, GilesItemType thisgilesitemtype, double ithisitemvalue)
        {
            FileStream LogStream = null;
            try
            {
                LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - JunkLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read);
                using (StreamWriter LogWriter = new StreamWriter(LogStream))
                {
                    if (!bLoggedJunkThisStash)
                    {
                        bLoggedJunkThisStash = true;
                        LogWriter.WriteLine(DateTime.Now.ToString() + ":");
                        LogWriter.WriteLine("====================");
                    }
                    string sLegendaryString = "";
                    if (thisgooditem.ThisQuality >= ItemQuality.Legendary)
                        sLegendaryString = " {legendary item}";
                    LogWriter.WriteLine(thisgilesbaseitemtype.ToString() + " - " + thisgilesitemtype.ToString() + " '" + thisgooditem.ThisRealName + "'. Score = " + Math.Round(ithisitemvalue).ToString() + sLegendaryString);
                    if (sJunkItemStatString != "")
                        LogWriter.WriteLine("  " + sJunkItemStatString);
                    else
                        LogWriter.WriteLine("  (no scorable attributes)");
                    LogWriter.WriteLine("");
                }
                LogStream.Close();
            }
            catch (IOException)
            {
                Log("Fatal Error: File access error for junk log file.");
                if (LogStream != null)
                    LogStream.Close();
            }
        }

        // **********************************************************************************************
        // *****          Stash replacement accurately and neatly finds a free stash location       *****
        // **********************************************************************************************
        private static bool GilesStashAttempt(GilesCachedACDItem item)
        {
            int iPlayerDynamicID = ZetaDia.Me.CommonData.DynamicId;
            int iOriginalGameBalanceId = item.ThisBalanceID;
            int iOriginalDynamicID = item.ThisDynamicID;
            int iOriginalStackQuantity = item.ThisItemStackQuantity;
            string sOriginalItemName = item.ThisRealName;
            string sOriginalInternalName = item.ThisInternalName;
            GilesItemType OriginalGilesItemType = DetermineItemType(item.ThisInternalName, item.ThisDBItemType, item.ThisFollowerType);
            GilesBaseItemType thisGilesBaseType = DetermineBaseType(OriginalGilesItemType);
            bool bOriginalTwoSlot = DetermineIsTwoSlot(OriginalGilesItemType);
            bool bOriginalIsStackable = DetermineIsStackable(OriginalGilesItemType);
            int iAttempts;
            if (_dictItemStashAttempted.TryGetValue(iOriginalDynamicID, out iAttempts))
            {
                Log("GSError: Detected a duplicate stash attempt, DB item mis-read error, now forcing this item as a 2-slot item");
                _dictItemStashAttempted[iOriginalDynamicID] = iAttempts + 1;
                bOriginalTwoSlot = true;
                bOriginalIsStackable = false;
                if (iAttempts > 6)
                {
                    Log("GSError: Detected an item stash loop risk, now re-mapping stash treating everything as 2-slot and re-attempting");
                    // Array for what blocks are or are not blocked
                    for (int iRow = 0; iRow <= 29; iRow++)
                        for (int iColumn = 0; iColumn <= 6; iColumn++)
                            GilesStashSlotBlocked[iColumn, iRow] = false;
                    // Block off the entire of any "protected stash pages"
                    foreach (int iProtPage in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedStashPages)
                        for (int iProtRow = 0; iProtRow <= 9; iProtRow++)
                            for (int iProtColumn = 0; iProtColumn <= 6; iProtColumn++)
                                GilesStashSlotBlocked[iProtColumn, iProtRow + (iProtPage * 10)] = true;
                    // Remove rows we don't have
                    for (int iRow = (ZetaDia.Me.NumSharedStashSlots / 7); iRow <= 29; iRow++)
                        for (int iColumn = 0; iColumn <= 6; iColumn++)
                            GilesStashSlotBlocked[iColumn, iRow] = true;
                    // Map out all the items already in the stash
                    foreach (ACDItem tempitem in ZetaDia.Me.Inventory.StashItems)
                    {
                        if (tempitem.BaseAddress != IntPtr.Zero)
                        {
                            int inventoryRow = tempitem.InventoryRow;
                            int inventoryColumn = tempitem.InventoryColumn;
                            // Mark this slot as not-free
                            GilesStashSlotBlocked[inventoryColumn, inventoryRow] = true;
                            // Try and reliably find out if this is a two slot item or not
                            GilesStashSlotBlocked[inventoryColumn, inventoryRow + 1] = true;
                            if (inventoryRow != 19 && inventoryRow != 9 && inventoryRow != 29)
                            {
                                GilesStashSlotBlocked[inventoryColumn, inventoryRow + 1] = true;
                            }
                        }
                    }
                }
                if (iAttempts > 15)
                {
                    Log("***************************");
                    Log("GSError: Emergency Stop: No matter what we tried, we couldn't prevent an infinite stash loop. Sorry. Now stopping the bot.");
                    BotMain.Stop();
                    return false;
                }
            }
            else
            {
                _dictItemStashAttempted.Add(iOriginalDynamicID, 1);
            }
            // Safety incase it's not actually in the backpack anymore
            /*if (item.InventorySlot != InventorySlot.PlayerBackpack)
            {
                Log("GSError: Diablo 3 memory read error, or item became invalid [StashAttempt-4]", true);
                return false;
            }*/
            int iLeftoverStackQuantity = 0;
            // Item log for cool stuff stashed
            if (thisGilesBaseType == GilesBaseItemType.WeaponTwoHand || thisGilesBaseType == GilesBaseItemType.WeaponOneHand || thisGilesBaseType == GilesBaseItemType.WeaponRange ||
                thisGilesBaseType == GilesBaseItemType.Armor || thisGilesBaseType == GilesBaseItemType.Jewelry || thisGilesBaseType == GilesBaseItemType.Offhand ||
                thisGilesBaseType == GilesBaseItemType.FollowerItem)
            {
                double iThisItemValue = ValueThisItem(item, OriginalGilesItemType);
                LogGoodItems(item, thisGilesBaseType, OriginalGilesItemType, iThisItemValue);
            }
            int iPointX = -1;
            int iPointY = -1;
            // First check if we can top-up any already-existing stacks in the stash
            if (bOriginalIsStackable)
            {
                foreach (ACDItem tempitem in ZetaDia.Me.Inventory.StashItems)
                {
                    if (tempitem.BaseAddress == IntPtr.Zero)
                    {
                        Log("GSError: Diablo 3 memory read error, or stash item became invalid [StashAttempt-5]", true);
                        return false;
                    }
                    // Check if we combine the stacks, we won't overfill them
                    if ((tempitem.GameBalanceId == iOriginalGameBalanceId) && (tempitem.ItemStackQuantity < tempitem.MaxStackCount))
                    {
                        // Will we have leftovers?
                        if ((tempitem.ItemStackQuantity + iOriginalStackQuantity) > tempitem.MaxStackCount)
                        {
                            iLeftoverStackQuantity = (tempitem.ItemStackQuantity + iOriginalStackQuantity) - tempitem.MaxStackCount;
                        }
                        iPointX = tempitem.InventoryColumn;
                        iPointY = tempitem.InventoryRow;
                        goto HandleStackMovement;
                    }
                }
            HandleStackMovement:
                if ((iPointX >= 0) && (iPointY >= 0))
                {
                    ZetaDia.Me.Inventory.MoveItem(iOriginalDynamicID, iPlayerDynamicID, InventorySlot.PlayerSharedStash, iPointX, iPointY);
                    // Only return if we have emptied this stack
                    if (iLeftoverStackQuantity <= 0)
                    {
                        return true;
                    }
                }
            }
            iPointX = -1;
            iPointY = -1;
            // If it's a 2-square item, find a double-slot free
            if (bOriginalTwoSlot)
            {
                for (int iRow = 0; iRow <= 29; iRow++)
                {
                    bool bBottomPageRow = (iRow == 9 || iRow == 19 || iRow == 29);
                    for (int iColumn = 0; iColumn <= 6; iColumn++)
                    {
                        // If nothing in the 1st row 
                        if (!GilesStashSlotBlocked[iColumn, iRow])
                        {
                            bool bNotEnoughSpace = false;
                            // Bottom row of a page = no room
                            if (bBottomPageRow)
                                bNotEnoughSpace = true;
                            // Already something in the stash in the 2nd row)
                            else if (GilesStashSlotBlocked[iColumn, iRow + 1])
                                bNotEnoughSpace = true;
                            if (!bNotEnoughSpace)
                            {
                                iPointX = iColumn;
                                iPointY = iRow;
                                goto FoundStashLocation;
                            }
                        }
                    }
                }
            } // 2 slot item?
            // Now deal with any leftover 1-slot items
            else
            {
                // First we try and find somewhere "sensible"
                for (int iRow = 0; iRow <= 29; iRow++)
                {
                    bool bTopPageRow = (iRow == 0 || iRow == 10 || iRow == 20);
                    bool bBottomPageRow = (iRow == 9 || iRow == 19 || iRow == 29);
                    for (int iColumn = 0; iColumn <= 6; iColumn++)
                    {
                        // Nothing in this slot
                        if (!GilesStashSlotBlocked[iColumn, iRow])
                        {
                            bool bSensibleLocation = false;
                            if (!bTopPageRow && !bBottomPageRow)
                            {
                                // Something above and below this slot, or an odd-numbered row, so put something here
                                if ((GilesStashSlotBlocked[iColumn, iRow + 1] && GilesStashSlotBlocked[iColumn, iRow - 1]) ||
                                    (iRow) % 2 != 0)
                                    bSensibleLocation = true;
                            }
                            // Top page row with something directly underneath already blocking
                            else if (bTopPageRow)
                            {
                                if (GilesStashSlotBlocked[iColumn, iRow + 1])
                                    bSensibleLocation = true;
                            }
                            // Bottom page row with something directly over already blocking
                            else
                            {
                                bSensibleLocation = true;
                            }
                            // Sensible location? Yay, stash it here!
                            if (bSensibleLocation)
                            {
                                iPointX = iColumn;
                                iPointY = iRow;
                                // Keep looking for places if it's a stackable to try to stick it at the end
                                if (!bOriginalIsStackable)
                                    goto FoundStashLocation;
                            }
                        }
                    }
                }
                // Didn't find a "sensible" place, let's try and force it in absolutely anywhere
                if ((iPointX < 0) || (iPointY < 0))
                {
                    for (int iRow = 0; iRow <= 29; iRow++)
                    {
                        for (int iColumn = 0; iColumn <= 6; iColumn++)
                        {
                            // Nothing in this spot, we're good!
                            if (!GilesStashSlotBlocked[iColumn, iRow])
                            {
                                iPointX = iColumn;
                                iPointY = iRow;
                                // Keep looking for places if it's a stackable to try to stick it at the end
                                if (!bOriginalIsStackable)
                                    goto FoundStashLocation;
                            }
                        }
                    }
                }
            }
        FoundStashLocation:
            if ((iPointX < 0) || (iPointY < 0))
            {
                Log("Fatal Error: No valid stash location found for '" + sOriginalItemName + "' [" + sOriginalInternalName + " - " + OriginalGilesItemType.ToString() + "]", true);
                Log("***************************");
                Log("GSError: Emergency Stop: You need to stash an item but no valid space could be found. Stash is full? Stopping the bot to prevent infinite town-run loop.");
                BotMain.Stop();
                return false;
            }
            // We have two valid points that are empty, move the object here!
            GilesStashSlotBlocked[iPointX, iPointY] = true;
            if (bOriginalTwoSlot)
                GilesStashSlotBlocked[iPointX, iPointY + 1] = true;
            ZetaDia.Me.Inventory.MoveItem(iOriginalDynamicID, iPlayerDynamicID, InventorySlot.PlayerSharedStash, iPointX, iPointY);
            return true;
        } // Custom stashing routine


        // **********************************************************************************************
        // *****                              Full Output Of Item Stats                             *****
        // **********************************************************************************************

        private static void OutputReport()
        {
            TimeSpan TotalRunningTime = DateTime.Now.Subtract(ItemStatsWhenStartedBot);
            string sLogFileName = ZetaDia.Service.CurrentHero.BattleTagName + " - Stats - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log";

            // Create whole new file
            FileStream LogStream = File.Open(sTrinityPluginPath + sLogFileName, FileMode.Create, FileAccess.Write, FileShare.Read);
            using (StreamWriter LogWriter = new StreamWriter(LogStream))
            {
                LogWriter.WriteLine("===== Misc Statistics =====");
                LogWriter.WriteLine("Total tracking time: " + TotalRunningTime.Hours.ToString() + "h " + TotalRunningTime.Minutes.ToString() +
                    "m " + TotalRunningTime.Seconds.ToString() + "s");
                LogWriter.WriteLine("Total deaths: " + iTotalDeaths.ToString() + " [" + Math.Round(iTotalDeaths / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                LogWriter.WriteLine("Total games (approx): " + iTotalLeaveGames.ToString() + " [" + Math.Round(iTotalLeaveGames / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                if (iTotalLeaveGames == 0 && iTotalJoinGames > 0)
                {
                    if (iTotalJoinGames == 1 && iTotalProfileRecycles > 1)
                    {
                        LogWriter.WriteLine("(a profile manager/death handler is interfering with join/leave game events, attempting to guess total runs based on profile-loops)");
                        LogWriter.WriteLine("Total full profile cycles: " + iTotalProfileRecycles.ToString() + " [" + Math.Round(iTotalProfileRecycles / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                    }
                    else
                    {
                        LogWriter.WriteLine("(your games left value may be bugged @ 0 due to profile managers/routines etc., now showing games joined instead:)");
                        LogWriter.WriteLine("Total games joined: " + iTotalJoinGames.ToString() + " [" + Math.Round(iTotalJoinGames / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                    }
                }
                LogWriter.WriteLine("");

                LogWriter.WriteLine("===== Item DROP Statistics =====");
                // Item stats
                if (ItemsDroppedStats.iTotal > 0)
                {
                    LogWriter.WriteLine("Items:");
                    LogWriter.WriteLine("Total items dropped: " + ItemsDroppedStats.iTotal.ToString() + " [" +
                        Math.Round(ItemsDroppedStats.iTotal / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");

                    LogWriter.WriteLine("Items dropped by ilvl: ");
                    for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++)
                        if (ItemsDroppedStats.iTotalPerLevel[iThisLevel] > 0)
                            LogWriter.WriteLine("- ilvl" + iThisLevel.ToString() + ": " + ItemsDroppedStats.iTotalPerLevel[iThisLevel].ToString() + " [" +
                                Math.Round(ItemsDroppedStats.iTotalPerLevel[iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" +
                                Math.Round((ItemsDroppedStats.iTotalPerLevel[iThisLevel] / ItemsDroppedStats.iTotal) * 100, 2).ToString() + " %}");

                    LogWriter.WriteLine("");

                    LogWriter.WriteLine("Items dropped by quality: ");
                    for (int iThisQuality = 0; iThisQuality <= 3; iThisQuality++)
                    {
                        if (ItemsDroppedStats.iTotalPerQuality[iThisQuality] > 0)
                        {
                            LogWriter.WriteLine("- " + sQualityString[iThisQuality] + ": " + ItemsDroppedStats.iTotalPerQuality[iThisQuality].ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalPerQuality[iThisQuality] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iTotalPerQuality[iThisQuality] / ItemsDroppedStats.iTotal) * 100, 2).ToString() + " %}");
                            for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++)
                                if (ItemsDroppedStats.iTotalPerQPerL[iThisQuality, iThisLevel] > 0)
                                    LogWriter.WriteLine("--- ilvl " + iThisLevel.ToString() + " " + sQualityString[iThisQuality] + ": " + ItemsDroppedStats.iTotalPerQPerL[iThisQuality, iThisLevel].ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalPerQPerL[iThisQuality, iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iTotalPerQPerL[iThisQuality, iThisLevel] / ItemsDroppedStats.iTotal) * 100, 2).ToString() + " %}");
                        } // Any at all this quality?
                    } // For loop on quality
                    LogWriter.WriteLine("");


                } // End of item stats
                // Potion stats
                if (ItemsDroppedStats.iTotalPotions > 0)
                {
                    LogWriter.WriteLine("Potion Drops:");
                    LogWriter.WriteLine("Total potions: " + ItemsDroppedStats.iTotalPotions.ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalPotions / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                    for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsDroppedStats.iPotionsPerLevel[iThisLevel] > 0)
                            LogWriter.WriteLine("- ilvl " + iThisLevel.ToString() + ": " + ItemsDroppedStats.iPotionsPerLevel[iThisLevel].ToString() + " [" + Math.Round(ItemsDroppedStats.iPotionsPerLevel[iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iPotionsPerLevel[iThisLevel] / ItemsDroppedStats.iTotalPotions) * 100, 2).ToString() + " %}");
                    LogWriter.WriteLine("");
                } // End of potion stats
                // Gem stats
                if (ItemsDroppedStats.iTotalGems > 0)
                {
                    LogWriter.WriteLine("Gem Drops:");
                    LogWriter.WriteLine("Total gems: " + ItemsDroppedStats.iTotalGems.ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalGems / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                    for (int iThisGemType = 0; iThisGemType <= 3; iThisGemType++)
                    {
                        if (ItemsDroppedStats.iGemsPerType[iThisGemType] > 0)
                        {
                            LogWriter.WriteLine("- " + sGemString[iThisGemType] + ": " + ItemsDroppedStats.iGemsPerType[iThisGemType].ToString() + " [" + Math.Round(ItemsDroppedStats.iGemsPerType[iThisGemType] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iGemsPerType[iThisGemType] / ItemsDroppedStats.iTotalGems) * 100, 2).ToString() + " %}");
                            for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++)
                                if (ItemsDroppedStats.iGemsPerTPerL[iThisGemType, iThisLevel] > 0)
                                    LogWriter.WriteLine("--- ilvl " + iThisLevel.ToString() + " " + sGemString[iThisGemType] + ": " + ItemsDroppedStats.iGemsPerTPerL[iThisGemType, iThisLevel].ToString() + " [" + Math.Round(ItemsDroppedStats.iGemsPerTPerL[iThisGemType, iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iGemsPerTPerL[iThisGemType, iThisLevel] / ItemsDroppedStats.iTotalGems) * 100, 2).ToString() + " %}");
                        } // Any at all this quality?
                    } // For loop on quality
                } // End of gem stats
                // Key stats
                if (ItemsDroppedStats.iTotalInfernalKeys > 0)
                {
                    LogWriter.WriteLine("Infernal Key Drops:");
                    LogWriter.WriteLine("Total Keys: " + ItemsDroppedStats.iTotalInfernalKeys.ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalInfernalKeys / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                } // End of key stats
                LogWriter.WriteLine("");
                LogWriter.WriteLine("");
                LogWriter.WriteLine("===== Item PICKUP Statistics =====");
                // Item stats
                if (ItemsPickedStats.iTotal > 0)
                {
                    LogWriter.WriteLine("Items:");
                    LogWriter.WriteLine("Total items picked up: " + ItemsPickedStats.iTotal.ToString() + " [" + Math.Round(ItemsPickedStats.iTotal / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");


                    LogWriter.WriteLine("Item picked up by ilvl: ");
                    for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++)
                        if (ItemsPickedStats.iTotalPerLevel[iThisLevel] > 0)
                            LogWriter.WriteLine("- ilvl" + iThisLevel.ToString() + ": " + ItemsPickedStats.iTotalPerLevel[iThisLevel].ToString() + " [" + Math.Round(ItemsPickedStats.iTotalPerLevel[iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iTotalPerLevel[iThisLevel] / ItemsPickedStats.iTotal) * 100, 2).ToString() + " %}");
                    LogWriter.WriteLine("");

                    LogWriter.WriteLine("Items picked up by quality: ");
                    for (int iThisQuality = 0; iThisQuality <= 3; iThisQuality++)
                    {
                        if (ItemsPickedStats.iTotalPerQuality[iThisQuality] > 0)
                        {
                            LogWriter.WriteLine("- " + sQualityString[iThisQuality] + ": " + ItemsPickedStats.iTotalPerQuality[iThisQuality].ToString() + " [" + Math.Round(ItemsPickedStats.iTotalPerQuality[iThisQuality] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iTotalPerQuality[iThisQuality] / ItemsPickedStats.iTotal) * 100, 2).ToString() + " %}");
                            for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++)
                                if (ItemsPickedStats.iTotalPerQPerL[iThisQuality, iThisLevel] > 0)
                                    LogWriter.WriteLine("--- ilvl " + iThisLevel.ToString() + " " + sQualityString[iThisQuality] + ": " + ItemsPickedStats.iTotalPerQPerL[iThisQuality, iThisLevel].ToString() + " [" + Math.Round(ItemsPickedStats.iTotalPerQPerL[iThisQuality, iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iTotalPerQPerL[iThisQuality, iThisLevel] / ItemsPickedStats.iTotal) * 100, 2).ToString() + " %}");
                        } // Any at all this quality?
                    } // For loop on quality
                    LogWriter.WriteLine("");
                    if (iTotalFollowerItemsIgnored > 0)
                    {
                        LogWriter.WriteLine("  (note: " + iTotalFollowerItemsIgnored.ToString() + " follower items ignored for being ilvl <60 or blue)");
                    }

                } // End of item stats
                // Potion stats
                if (ItemsPickedStats.iTotalPotions > 0)
                {
                    LogWriter.WriteLine("Potion Pickups:");
                    LogWriter.WriteLine("Total potions: " + ItemsPickedStats.iTotalPotions.ToString() + " [" + Math.Round(ItemsPickedStats.iTotalPotions / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                    for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsPickedStats.iPotionsPerLevel[iThisLevel] > 0)
                            LogWriter.WriteLine("- ilvl " + iThisLevel.ToString() + ": " + ItemsPickedStats.iPotionsPerLevel[iThisLevel].ToString() + " [" + Math.Round(ItemsPickedStats.iPotionsPerLevel[iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iPotionsPerLevel[iThisLevel] / ItemsPickedStats.iTotalPotions) * 100, 2).ToString() + " %}");
                    LogWriter.WriteLine("");
                } // End of potion stats
                // Gem stats
                if (ItemsPickedStats.iTotalGems > 0)
                {
                    LogWriter.WriteLine("Gem Pickups:");
                    LogWriter.WriteLine("Total gems: " + ItemsPickedStats.iTotalGems.ToString() + " [" + Math.Round(ItemsPickedStats.iTotalGems / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                    for (int iThisGemType = 0; iThisGemType <= 3; iThisGemType++)
                    {
                        if (ItemsPickedStats.iGemsPerType[iThisGemType] > 0)
                        {
                            LogWriter.WriteLine("- " + sGemString[iThisGemType] + ": " + ItemsPickedStats.iGemsPerType[iThisGemType].ToString() + " [" + Math.Round(ItemsPickedStats.iGemsPerType[iThisGemType] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iGemsPerType[iThisGemType] / ItemsPickedStats.iTotalGems) * 100, 2).ToString() + " %}");
                            for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++)
                                if (ItemsPickedStats.iGemsPerTPerL[iThisGemType, iThisLevel] > 0)
                                    LogWriter.WriteLine("--- ilvl " + iThisLevel.ToString() + " " + sGemString[iThisGemType] + ": " + ItemsPickedStats.iGemsPerTPerL[iThisGemType, iThisLevel].ToString() + " [" + Math.Round(ItemsPickedStats.iGemsPerTPerL[iThisGemType, iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iGemsPerTPerL[iThisGemType, iThisLevel] / ItemsPickedStats.iTotalGems) * 100, 2).ToString() + " %}");
                        } // Any at all this quality?
                    } // For loop on quality
                } // End of gem stats
                // Key stats
                if (ItemsPickedStats.iTotalInfernalKeys > 0)
                {
                    LogWriter.WriteLine("Infernal Key Pickups:");
                    LogWriter.WriteLine("Total Keys: " + ItemsPickedStats.iTotalInfernalKeys.ToString() + " [" + Math.Round(ItemsPickedStats.iTotalInfernalKeys / TotalRunningTime.TotalHours, 2).ToString() + " per hour]");
                } // End of key stats
                LogWriter.WriteLine("===== End Of Report =====");
            }
            LogStream.Close();
        }


        // **********************************************************************************************
        // *****          Search backpack to see if we have room for a 2-slot item anywhere         *****
        // **********************************************************************************************

        private static Vector2 FindValidBackpackLocation(bool bOriginalTwoSlot)
        {
            bool[,] GilesBackpackSlotBlocked = new bool[10, 6];
            // Block off the entire of any "protected bag slots"
            foreach (InventorySquare thissquare in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedBagSlots)
                GilesBackpackSlotBlocked[thissquare.Column, thissquare.Row] = true;
            // Map out all the items already in the backpack
            foreach (ACDItem tempitem in ZetaDia.Me.Inventory.Backpack)
            {
                if (tempitem.BaseAddress == IntPtr.Zero)
                {
                    return new Vector2(-1, -1);
                }
                int inventoryRow = tempitem.InventoryRow;
                int inventoryColumn = tempitem.InventoryColumn;
                // Mark this slot as not-free
                GilesBackpackSlotBlocked[inventoryColumn, inventoryRow] = true;
                // Try and reliably find out if this is a two slot item or not
                GilesItemType tempItemType = DetermineItemType(tempitem.InternalName, tempitem.ItemType, tempitem.FollowerSpecialType);
                if (DetermineIsTwoSlot(tempItemType) && inventoryRow < 5)
                {
                    GilesBackpackSlotBlocked[inventoryColumn, inventoryRow + 1] = true;
                }
            }
            int iPointX = -1;
            int iPointY = -1;
            for (int iRow = 0; iRow <= 5; iRow++)
            {
                for (int iColumn = 0; iColumn <= 9; iColumn++)
                {
                    if (!GilesBackpackSlotBlocked[iColumn, iRow])
                    {
                        bool bNotEnoughSpace = false;
                        if (iRow < 5)
                        {
                            bNotEnoughSpace = (bOriginalTwoSlot && GilesBackpackSlotBlocked[iColumn, iRow + 1]);
                        }
                        else
                        {
                            if (bOriginalTwoSlot)
                                bNotEnoughSpace = true;
                        }
                        if (!bNotEnoughSpace)
                        {
                            iPointX = iColumn;
                            iPointY = iRow;
                            goto FoundPackLocation;
                        }
                    }
                }
            }
        FoundPackLocation:
            if ((iPointX < 0) || (iPointY < 0))
            {
                return new Vector2(-1, -1);
            }
            return new Vector2(iPointX, iPointY);
        }




































        // **********************************************************************************************
        // *****                               Save Configuration                                   *****
        // **********************************************************************************************
        private void SaveConfiguration()
        {
            if (bSavingConfig) return;
            bSavingConfig = true;
            FileStream configStream = File.Open(sTrinityConfigFile, FileMode.Create, FileAccess.Write, FileShare.Read);
            using (StreamWriter configWriter = new StreamWriter(configStream))
            {
                configWriter.WriteLine("JewelryPoints=" + settings.iNeedPointsToKeepJewelry.ToString());
                configWriter.WriteLine("ArmorPoints=" + settings.iNeedPointsToKeepArmor.ToString());
                configWriter.WriteLine("WeaponPoints=" + settings.iNeedPointsToKeepWeapon.ToString());
                configWriter.WriteLine(settings.bSalvageJunk ? "Salvage=true" : "Salvage=false");
                configWriter.WriteLine(settings.bUseGilesFilters ? "Filters=true" : "Filters=false");
                configWriter.WriteLine(settings.bGemsEmerald ? "Emerald=true" : "Emerald=false");
                configWriter.WriteLine(settings.bGemsAmethyst ? "Amethyst=true" : "Amethyst=false");
                configWriter.WriteLine(settings.bGemsTopaz ? "Topaz=true" : "Topaz=false");
                configWriter.WriteLine(settings.bGemsRuby ? "Ruby=true" : "Ruby=false");
                configWriter.WriteLine(settings.bPickupCraftTomes ? "Tomes=true" : "Tomes=false");
                configWriter.WriteLine(settings.bPickupPlans ? "Plans=true" : "Plans=false");
                configWriter.WriteLine(settings.bPickupFollower ? "Followers=true" : "Followers=false");
                configWriter.WriteLine("Potions=" + settings.iFilterPotions.ToString());
                configWriter.WriteLine("ilvlPots=" + settings.iFilterPotionLevel.ToString());
                configWriter.WriteLine("ilvlLegendary=" + settings.iFilterLegendary.ToString());
                configWriter.WriteLine("ilvlWB=" + settings.iFilterBlueWeapons.ToString());
                configWriter.WriteLine("ilvlWY=" + settings.iFilterYellowWeapons.ToString());
                configWriter.WriteLine("ilvlAB=" + settings.iFilterBlueArmor.ToString());
                configWriter.WriteLine("ilvlAY=" + settings.iFilterYellowArmor.ToString());
                configWriter.WriteLine("ilvlJB=" + settings.iFilterBlueJewelry.ToString());
                configWriter.WriteLine("ilvlJY=" + settings.iFilterYellowJewelry.ToString());
                configWriter.WriteLine("ilvlGems=" + settings.iFilterGems.ToString());
                configWriter.WriteLine("ilvlMisc=" + settings.iFilterMisc.ToString());
                configWriter.WriteLine("GoldPickup=" + settings.iMinimumGoldStack.ToString());
                configWriter.WriteLine("GoblinPriority=" + settings.iTreasureGoblinPriority.ToString());
                configWriter.WriteLine("TriggerRange=" + settings.iMonsterKillRange.ToString());
                configWriter.WriteLine("LootDelay=" + settings.iKillLootDelay.ToString());
                configWriter.WriteLine("VaultDelay=" + settings.iDHVaultMovementDelay.ToString());
                configWriter.WriteLine("MonkInna=" + settings.bMonkInnaSet.ToString());
                configWriter.WriteLine("Avoidance=" + settings.bEnableAvoidance.ToString());
                configWriter.WriteLine("Globes=" + settings.bEnableGlobes.ToString());
                configWriter.WriteLine("CriticalMass=" + settings.bEnableCriticalMass.ToString());
                configWriter.WriteLine("OOCMovementPower=" + settings.bOutOfCombatMovementPowers.ToString());
                configWriter.WriteLine("ExtendedKills=" + settings.bExtendedKillRange.ToString());
                configWriter.WriteLine("SelectiveWW=" + settings.bSelectiveWhirlwind.ToString());
                configWriter.WriteLine("Wrath90=" + settings.bWrath90Seconds.ToString());
                configWriter.WriteLine("WizKiteArchonOnly=" + settings.bKiteOnlyArchon.ToString());
                configWriter.WriteLine("WizWaitForArchon=" + settings.bWaitForArchon.ToString());
                configWriter.WriteLine("BarbWaitForWrath=" + settings.bWaitForWrath.ToString());
                configWriter.WriteLine("BarbGoblinWrath=" + settings.bGoblinWrath.ToString());
                configWriter.WriteLine("BarbFuryDumpWrath=" + settings.bFuryDumpWrath.ToString());
                configWriter.WriteLine("BarbFuryDumpAlways=" + settings.bFuryDumpAlways.ToString());
                configWriter.WriteLine("LogStucks=" + settings.bLogStucks.ToString());
                configWriter.WriteLine("Unstucker=" + settings.bEnableUnstucker.ToString());
                configWriter.WriteLine("ProfileReloading=" + settings.bEnableProfileReloading.ToString());
                configWriter.WriteLine("Backtracking=" + settings.bEnableBacktracking.ToString());
                configWriter.WriteLine(settings.bIgnoreAllShrines ? "ShrineIgnore=all" : "ShrineIgnore=none");
                configWriter.WriteLine("ContainerRange=" + settings.iContainerOpenRange.ToString());
                configWriter.WriteLine("DestructibleRange=" + settings.iDestructibleAttackRange.ToString());
                configWriter.WriteLine("IgnoreCorpses=" + settings.bIgnoreCorpses.ToString());
                configWriter.WriteLine(settings.bEnableTPS ? "TPSEnabled=true" : "TPSEnabled=false");
                configWriter.WriteLine("TPSAmount=" + settings.iTPSAmount.ToString());
                configWriter.WriteLine(settings.bDebugInfo ? "DebugInfo=true" : "DebugInfo=false");
                configWriter.WriteLine(settings.bEnableProwl ? "EnableProwl=true" : "EnableProwl=false");
                configWriter.WriteLine(settings.bEnableAndroid ? "EnableAndroid=true" : "EnableAndroid=false");
                configWriter.WriteLine(settings.bEnableEmail ? "EnableEmail=true" : "EnableEmail=false");
                configWriter.WriteLine("EmailAddress=" + sEmailAddress);
                configWriter.WriteLine("EmailPassword=" + sEmailPassword);
                configWriter.WriteLine("ProwlKey=" + sProwlAPIKey);
                configWriter.WriteLine("AndroidKey=" + sAndroidAPIKey);
                configWriter.WriteLine("EnableLegendaryNotifyScore=" + (settings.bEnableLegendaryNotifyScore ? "true" : "false"));
                configWriter.WriteLine("JewelryNotify=" + settings.iNeedPointsToNotifyJewelry.ToString());
                configWriter.WriteLine("ArmorNotify=" + settings.iNeedPointsToNotifyArmor.ToString());
                configWriter.WriteLine("WeaponNotify=" + settings.iNeedPointsToNotifyWeapon.ToString());
                configWriter.WriteLine("KiteBarb=" + settings.iKiteDistanceBarb.ToString());
                configWriter.WriteLine("KiteWiz=" + settings.iKiteDistanceWiz.ToString());
                configWriter.WriteLine("KiteWitch=" + settings.iKiteDistanceWitch.ToString());
                configWriter.WriteLine("KiteDemon=" + settings.iKiteDistanceDemon.ToString());
                configWriter.WriteLine("PotBarb=" + settings.dEmergencyHealthPotionBarb.ToString());
                configWriter.WriteLine("PotMonk=" + settings.dEmergencyHealthPotionMonk.ToString());
                configWriter.WriteLine("PotWiz=" + settings.dEmergencyHealthPotionWiz.ToString());
                configWriter.WriteLine("PotWitch=" + settings.dEmergencyHealthPotionWitch.ToString());
                configWriter.WriteLine("PotDemon=" + settings.dEmergencyHealthPotionDemon.ToString());
                configWriter.WriteLine("GlobeBarb=" + settings.dEmergencyHealthGlobeBarb.ToString());
                configWriter.WriteLine("GlobeMonk=" + settings.dEmergencyHealthGlobeMonk.ToString());
                configWriter.WriteLine("GlobeWiz=" + settings.dEmergencyHealthGlobeWiz.ToString());
                configWriter.WriteLine("GlobeWitch=" + settings.dEmergencyHealthGlobeWitch.ToString());
                configWriter.WriteLine("GlobeDemon=" + settings.dEmergencyHealthGlobeDemon.ToString());
                string sHealthLine = "";
                for (int i = 1; i <= 13; i++)
                {
                    switch (i)
                    {
                        case 1:
                            sHealthLine += dictAvoidanceHealthBarb[219702].ToString();
                            break;
                        case 2:
                            sHealthLine += dictAvoidanceHealthBarb[84608].ToString();
                            break;
                        case 3:
                            sHealthLine += dictAvoidanceHealthBarb[4804].ToString();
                            break;
                        case 4:
                            sHealthLine += dictAvoidanceHealthBarb[95868].ToString();
                            break;
                        case 5:
                            sHealthLine += dictAvoidanceHealthBarb[5482].ToString();
                            break;
                        case 6:
                            sHealthLine += dictAvoidanceHealthBarb[108869].ToString();
                            break;
                        case 7:
                            sHealthLine += dictAvoidanceHealthBarb[223675].ToString();
                            break;
                        case 8:
                            sHealthLine += dictAvoidanceHealthBarb[3865].ToString();
                            break;
                        case 9:
                            sHealthLine += dictAvoidanceHealthBarb[5212].ToString();
                            break;
                        case 10:
                            sHealthLine += dictAvoidanceHealthBarb[123124].ToString();
                            break;
                        case 11:
                            sHealthLine += dictAvoidanceHealthBarb[123839].ToString();
                            break;
                        case 12:
                            sHealthLine += dictAvoidanceHealthBarb[4103].ToString();
                            break;
                        case 13:
                            sHealthLine += dictAvoidanceHealthBarb[93837].ToString();
                            break;
                    }
                    if (i < 13)
                        sHealthLine += " ";
                }
                configWriter.WriteLine("AOEBarbHealth=" + sHealthLine);

                sHealthLine = "";
                for (int i = 1; i <= 13; i++)
                {
                    switch (i)
                    {
                        case 1:
                            sHealthLine += dictAvoidanceHealthMonk[219702].ToString();
                            break;
                        case 2:
                            sHealthLine += dictAvoidanceHealthMonk[84608].ToString();
                            break;
                        case 3:
                            sHealthLine += dictAvoidanceHealthMonk[4804].ToString();
                            break;
                        case 4:
                            sHealthLine += dictAvoidanceHealthMonk[95868].ToString();
                            break;
                        case 5:
                            sHealthLine += dictAvoidanceHealthMonk[5482].ToString();
                            break;
                        case 6:
                            sHealthLine += dictAvoidanceHealthMonk[108869].ToString();
                            break;
                        case 7:
                            sHealthLine += dictAvoidanceHealthMonk[223675].ToString();
                            break;
                        case 8:
                            sHealthLine += dictAvoidanceHealthMonk[3865].ToString();
                            break;
                        case 9:
                            sHealthLine += dictAvoidanceHealthMonk[5212].ToString();
                            break;
                        case 10:
                            sHealthLine += dictAvoidanceHealthMonk[123124].ToString();
                            break;
                        case 11:
                            sHealthLine += dictAvoidanceHealthMonk[123839].ToString();
                            break;
                        case 12:
                            sHealthLine += dictAvoidanceHealthMonk[4103].ToString();
                            break;
                        case 13:
                            sHealthLine += dictAvoidanceHealthMonk[93837].ToString();
                            break;
                    }
                    if (i < 13)
                        sHealthLine += " ";
                }
                configWriter.WriteLine("AOEMonkHealth=" + sHealthLine);

                sHealthLine = "";
                for (int i = 1; i <= 13; i++)
                {
                    switch (i)
                    {
                        case 1:
                            sHealthLine += dictAvoidanceHealthWizard[219702].ToString();
                            break;
                        case 2:
                            sHealthLine += dictAvoidanceHealthWizard[84608].ToString();
                            break;
                        case 3:
                            sHealthLine += dictAvoidanceHealthWizard[4804].ToString();
                            break;
                        case 4:
                            sHealthLine += dictAvoidanceHealthWizard[95868].ToString();
                            break;
                        case 5:
                            sHealthLine += dictAvoidanceHealthWizard[5482].ToString();
                            break;
                        case 6:
                            sHealthLine += dictAvoidanceHealthWizard[108869].ToString();
                            break;
                        case 7:
                            sHealthLine += dictAvoidanceHealthWizard[223675].ToString();
                            break;
                        case 8:
                            sHealthLine += dictAvoidanceHealthWizard[3865].ToString();
                            break;
                        case 9:
                            sHealthLine += dictAvoidanceHealthWizard[5212].ToString();
                            break;
                        case 10:
                            sHealthLine += dictAvoidanceHealthWizard[123124].ToString();
                            break;
                        case 11:
                            sHealthLine += dictAvoidanceHealthWizard[123839].ToString();
                            break;
                        case 12:
                            sHealthLine += dictAvoidanceHealthWizard[4103].ToString();
                            break;
                        case 13:
                            sHealthLine += dictAvoidanceHealthWizard[93837].ToString();
                            break;
                    }
                    if (i < 13)
                        sHealthLine += " ";
                }
                configWriter.WriteLine("AOEWizardHealth=" + sHealthLine);

                sHealthLine = "";
                for (int i = 1; i <= 13; i++)
                {
                    switch (i)
                    {
                        case 1:
                            sHealthLine += dictAvoidanceHealthWitch[219702].ToString();
                            break;
                        case 2:
                            sHealthLine += dictAvoidanceHealthWitch[84608].ToString();
                            break;
                        case 3:
                            sHealthLine += dictAvoidanceHealthWitch[4804].ToString();
                            break;
                        case 4:
                            sHealthLine += dictAvoidanceHealthWitch[95868].ToString();
                            break;
                        case 5:
                            sHealthLine += dictAvoidanceHealthWitch[5482].ToString();
                            break;
                        case 6:
                            sHealthLine += dictAvoidanceHealthWitch[108869].ToString();
                            break;
                        case 7:
                            sHealthLine += dictAvoidanceHealthWitch[223675].ToString();
                            break;
                        case 8:
                            sHealthLine += dictAvoidanceHealthWitch[3865].ToString();
                            break;
                        case 9:
                            sHealthLine += dictAvoidanceHealthWitch[5212].ToString();
                            break;
                        case 10:
                            sHealthLine += dictAvoidanceHealthWitch[123124].ToString();
                            break;
                        case 11:
                            sHealthLine += dictAvoidanceHealthWitch[123839].ToString();
                            break;
                        case 12:
                            sHealthLine += dictAvoidanceHealthWitch[4103].ToString();
                            break;
                        case 13:
                            sHealthLine += dictAvoidanceHealthWitch[93837].ToString();
                            break;
                    }
                    if (i < 13)
                        sHealthLine += " ";
                }
                configWriter.WriteLine("AOEWitchHealth=" + sHealthLine);


                sHealthLine = "";
                for (int i = 1; i <= 13; i++)
                {
                    switch (i)
                    {
                        case 1:
                            sHealthLine += dictAvoidanceHealthDemon[219702].ToString();
                            break;
                        case 2:
                            sHealthLine += dictAvoidanceHealthDemon[84608].ToString();
                            break;
                        case 3:
                            sHealthLine += dictAvoidanceHealthDemon[4804].ToString();
                            break;
                        case 4:
                            sHealthLine += dictAvoidanceHealthDemon[95868].ToString();
                            break;
                        case 5:
                            sHealthLine += dictAvoidanceHealthDemon[5482].ToString();
                            break;
                        case 6:
                            sHealthLine += dictAvoidanceHealthDemon[108869].ToString();
                            break;
                        case 7:
                            sHealthLine += dictAvoidanceHealthDemon[223675].ToString();
                            break;
                        case 8:
                            sHealthLine += dictAvoidanceHealthDemon[3865].ToString();
                            break;
                        case 9:
                            sHealthLine += dictAvoidanceHealthDemon[5212].ToString();
                            break;
                        case 10:
                            sHealthLine += dictAvoidanceHealthDemon[123124].ToString();
                            break;
                        case 11:
                            sHealthLine += dictAvoidanceHealthDemon[123839].ToString();
                            break;
                        case 12:
                            sHealthLine += dictAvoidanceHealthDemon[4103].ToString();
                            break;
                        case 13:
                            sHealthLine += dictAvoidanceHealthDemon[93837].ToString();
                            break;
                    }
                    if (i < 13)
                        sHealthLine += " ";
                }
                configWriter.WriteLine("AOEDemonHealth=" + sHealthLine);

                sHealthLine = "";
                for (int i = 1; i <= 13; i++)
                {
                    switch (i)
                    {
                        case 1:
                            sHealthLine += dictAvoidanceRadius[219702].ToString();
                            break;
                        case 2:
                            sHealthLine += dictAvoidanceRadius[84608].ToString();
                            break;
                        case 3:
                            sHealthLine += dictAvoidanceRadius[4804].ToString();
                            break;
                        case 4:
                            sHealthLine += dictAvoidanceRadius[95868].ToString();
                            break;
                        case 5:
                            sHealthLine += dictAvoidanceRadius[5482].ToString();
                            break;
                        case 6:
                            sHealthLine += dictAvoidanceRadius[108869].ToString();
                            break;
                        case 7:
                            sHealthLine += dictAvoidanceRadius[223675].ToString();
                            break;
                        case 8:
                            sHealthLine += dictAvoidanceRadius[3865].ToString();
                            break;
                        case 9:
                            sHealthLine += dictAvoidanceRadius[5212].ToString();
                            break;
                        case 10:
                            sHealthLine += dictAvoidanceRadius[123124].ToString();
                            break;
                        case 11:
                            sHealthLine += dictAvoidanceRadius[123839].ToString();
                            break;
                        case 12:
                            sHealthLine += dictAvoidanceRadius[4103].ToString();
                            break;
                        case 13:
                            sHealthLine += dictAvoidanceRadius[93837].ToString();
                            break;
                    }
                    if (i < 13)
                        sHealthLine += " ";
                }
                configWriter.WriteLine("AOERadius=" + sHealthLine);

            }
            configStream.Close();
            saveEmailConfiguration();
            bSavingConfig = false;
            bMappedPlayerAbilities = false;
        }

        private void saveEmailConfiguration()
        {
            FileStream emailConfigStream = File.Open(sTrinityEmailConfigFile, FileMode.Create, FileAccess.Write, FileShare.Read);
            using (StreamWriter configWriter = new StreamWriter(emailConfigStream))
            {
                configWriter.WriteLine(settings.bEnableEmail ? "EnableEmail=true" : "EnableEmail=false");
                configWriter.WriteLine("EmailAddress=" + sEmailAddress);
                configWriter.WriteLine("EmailPassword=" + sEmailPassword);
                configWriter.WriteLine("BotName=" + sBotName);
            }
            emailConfigStream.Close();
        }


        // **********************************************************************************************
        // *****                               Load Configuration                                   *****
        // **********************************************************************************************

        private void LoadConfiguration()
        {
            //Check for Config file
            if (!File.Exists(sTrinityConfigFile))
            {
                Log("No config file found, now creating a new config from defaults at: " + sTrinityConfigFile);
                SaveConfiguration();
                return;
            }
            //Load File
            string[] healthlevels;
            using (StreamReader configReader = new StreamReader(sTrinityConfigFile))
            {
                while (!configReader.EndOfStream)
                {
                    string[] config = configReader.ReadLine().Split('=');
                    if (config != null)
                    {
                        switch (config[0])
                        {
                            case "GoblinPriority":
                                settings.iTreasureGoblinPriority = Convert.ToInt32(config[1]);
                                break;
                            case "TriggerRange":
                                settings.iMonsterKillRange = Convert.ToDouble(config[1]);
                                break;
                            case "LootDelay":
                                settings.iKillLootDelay = Convert.ToInt32(config[1]);
                                break;
                            case "VaultDelay":
                                settings.iDHVaultMovementDelay = Convert.ToInt32(config[1]);
                                break;
                            case "MonkInna":
                                settings.bMonkInnaSet = Convert.ToBoolean(config[1]);
                                break;
                            case "Avoidance":
                                settings.bEnableAvoidance = Convert.ToBoolean(config[1]);
                                break;
                            case "Globes":
                                settings.bEnableGlobes = Convert.ToBoolean(config[1]);
                                break;
                            case "CriticalMass":
                                settings.bEnableCriticalMass = Convert.ToBoolean(config[1]);
                                break;
                            case "OOCMovementPower":
                                settings.bOutOfCombatMovementPowers = Convert.ToBoolean(config[1]);
                                break;
                            case "Backtracking":
                                settings.bEnableBacktracking = Convert.ToBoolean(config[1]);
                                break;
                            case "JewelryPoints":
                                settings.iNeedPointsToKeepJewelry = Convert.ToDouble(config[1]);
                                break;
                            case "ArmorPoints":
                                settings.iNeedPointsToKeepArmor = Convert.ToDouble(config[1]);
                                break;
                            case "WeaponPoints":
                                settings.iNeedPointsToKeepWeapon = Convert.ToDouble(config[1]);
                                break;
                            case "JewelryNotify":
                                settings.iNeedPointsToNotifyJewelry = Convert.ToDouble(config[1]);
                                break;
                            case "ArmorNotify":
                                settings.iNeedPointsToNotifyArmor = Convert.ToDouble(config[1]);
                                break;
                            case "WeaponNotify":
                                settings.iNeedPointsToNotifyWeapon = Convert.ToDouble(config[1]);
                                break;
                            case "Salvage":
                                settings.bSalvageJunk = Convert.ToBoolean(config[1]);
                                break;
                            case "Filters":
                                settings.bUseGilesFilters = Convert.ToBoolean(config[1]);
                                break;
                            case "Emerald":
                                settings.bGemsEmerald = Convert.ToBoolean(config[1]);
                                break;
                            case "Amethyst":
                                settings.bGemsAmethyst = Convert.ToBoolean(config[1]);
                                break;
                            case "Topaz":
                                settings.bGemsTopaz = Convert.ToBoolean(config[1]);
                                break;
                            case "Ruby":
                                settings.bGemsRuby = Convert.ToBoolean(config[1]);
                                break;
                            case "Tomes":
                                settings.bPickupCraftTomes = Convert.ToBoolean(config[1]);
                                break;
                            case "Plans":
                                settings.bPickupPlans = Convert.ToBoolean(config[1]);
                                break;
                            case "Followers":
                                settings.bPickupFollower = Convert.ToBoolean(config[1]);
                                break;
                            case "Potions":
                                settings.iFilterPotions = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlPots":
                                settings.iFilterPotionLevel = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlLegendary":
                                settings.iFilterLegendary = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlWB":
                                settings.iFilterBlueWeapons = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlWY":
                                settings.iFilterYellowWeapons = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlAB":
                                settings.iFilterBlueArmor = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlAY":
                                settings.iFilterYellowArmor = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlJB":
                                settings.iFilterBlueJewelry = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlJY":
                                settings.iFilterYellowJewelry = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlGems":
                                settings.iFilterGems = Convert.ToInt16(config[1]);
                                break;
                            case "ilvlMisc":
                                settings.iFilterMisc = Convert.ToInt16(config[1]);
                                break;
                            case "GoldPickup":
                                settings.iMinimumGoldStack = Convert.ToInt16(config[1]);
                                break;
                            case "ShrineIgnore":
                                settings.bIgnoreAllShrines = (config[1] == "all");
                                break;
                            case "ContainerRange":
                                settings.iContainerOpenRange = Convert.ToDouble(config[1]);
                                break;
                            case "DestructibleRange":
                                settings.iDestructibleAttackRange = Convert.ToDouble(config[1]);
                                break;
                            case "IgnoreCorpses":
                                settings.bIgnoreCorpses = Convert.ToBoolean(config[1]);
                                break;
                            case "TPSEnabled":
                                settings.bEnableTPS = Convert.ToBoolean(config[1]);
                                break;
                            case "TPSAmount":
                                settings.iTPSAmount = Convert.ToDouble(config[1]);
                                break;
                            case "DebugInfo":
                                settings.bDebugInfo = Convert.ToBoolean(config[1]);
                                break;
                            case "LogStucks":
                                settings.bLogStucks = Convert.ToBoolean(config[1]);
                                break;
                            case "ProfileReloading":
                                settings.bEnableProfileReloading = Convert.ToBoolean(config[1]);
                                break;
                            case "Unstucker":
                                settings.bEnableUnstucker = Convert.ToBoolean(config[1]);
                                if (settings.bEnableUnstucker)
                                    Navigator.StuckHandler = new GilesStuckHandler();
                                else
                                    Navigator.StuckHandler = new DefaultStuckHandler();
                                break;
                            case "ExtendedKills":
                                settings.bExtendedKillRange = Convert.ToBoolean(config[1]);
                                break;
                            case "SelectiveWW":
                                settings.bSelectiveWhirlwind = Convert.ToBoolean(config[1]);
                                break;
                            case "Wrath90":
                                settings.bWrath90Seconds = Convert.ToBoolean(config[1]);
                                break;
                            case "WizKiteArchonOnly":
                                settings.bKiteOnlyArchon = Convert.ToBoolean(config[1]);
                                break;
                            case "WizWaitForArchon":
                                settings.bWaitForArchon = Convert.ToBoolean(config[1]);
                                break;
                            case "BarbWaitForWrath":
                                settings.bWaitForWrath = Convert.ToBoolean(config[1]);
                                break;
                            case "BarbGoblinWrath":
                                settings.bGoblinWrath = Convert.ToBoolean(config[1]);
                                break;
                            case "BarbFuryDumpWrath":
                                settings.bFuryDumpWrath = Convert.ToBoolean(config[1]);
                                break;
                            case "BarbFuryDumpAlways":
                                settings.bFuryDumpAlways = Convert.ToBoolean(config[1]);
                                break;
                            case "EnableProwl":
                                settings.bEnableProwl = Convert.ToBoolean(config[1]);
                                break;
                            case "EnableLegendaryNotifyScore":
                                settings.bEnableLegendaryNotifyScore = Convert.ToBoolean(config[1]);
                                break;
                            case "ProwlKey":
                                sProwlAPIKey = config[1];
                                break;
                            case "EnableEmail":
                                settings.bEnableEmail = Convert.ToBoolean(config[1]);
                                break;
                            case "EmailAddress":
                                sEmailAddress = config[1];
                                break;
                            case "EmailPassword":
                                sEmailPassword = config[1];
                                break;
                            case "EnableAndroid":
                                settings.bEnableAndroid = Convert.ToBoolean(config[1]);
                                break;
                            case "AndroidKey":
                                sAndroidAPIKey = config[1];
                                break;
                            case "KiteBarb":
                                settings.iKiteDistanceBarb = Convert.ToInt32(config[1]);
                                break;
                            case "KiteWiz":
                                settings.iKiteDistanceWiz = Convert.ToInt32(config[1]);
                                break;
                            case "KiteWitch":
                                settings.iKiteDistanceWitch = Convert.ToInt32(config[1]);
                                break;
                            case "KiteDemon":
                                settings.iKiteDistanceDemon = Convert.ToInt32(config[1]);
                                break;
                            case "PotBarb":
                                settings.dEmergencyHealthPotionBarb = Convert.ToDouble(config[1]);
                                break;
                            case "PotMonk":
                                settings.dEmergencyHealthPotionMonk = Convert.ToDouble(config[1]);
                                break;
                            case "PotWiz":
                                settings.dEmergencyHealthPotionWiz = Convert.ToDouble(config[1]);
                                break;
                            case "PotWitch":
                                settings.dEmergencyHealthPotionWitch = Convert.ToDouble(config[1]);
                                break;
                            case "PotDemon":
                                settings.dEmergencyHealthPotionDemon = Convert.ToDouble(config[1]);
                                break;
                            case "GlobeBarb":
                                settings.dEmergencyHealthGlobeBarb = Convert.ToDouble(config[1]);
                                break;
                            case "GlobeMonk":
                                settings.dEmergencyHealthGlobeMonk = Convert.ToDouble(config[1]);
                                break;
                            case "GlobeWiz":
                                settings.dEmergencyHealthGlobeWiz = Convert.ToDouble(config[1]);
                                break;
                            case "GlobeWitch":
                                settings.dEmergencyHealthGlobeWitch = Convert.ToDouble(config[1]);
                                break;
                            case "GlobeDemon":
                                settings.dEmergencyHealthGlobeDemon = Convert.ToDouble(config[1]);
                                break;
                            case "AOEBarbHealth":
                                healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None);
                                for (int i = 1; i <= healthlevels.Length; i++)
                                {
                                    switch (i)
                                    {
                                        case 1:
                                            dictAvoidanceHealthBarb[219702] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthBarb[221225] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 2:
                                            dictAvoidanceHealthBarb[84608] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 3:
                                            dictAvoidanceHealthBarb[4803] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthBarb[4804] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthBarb[224225] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthBarb[247987] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 4:
                                            dictAvoidanceHealthBarb[95868] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 5:
                                            dictAvoidanceHealthBarb[5482] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthBarb[6578] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 6:
                                            dictAvoidanceHealthBarb[108869] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 7:
                                            dictAvoidanceHealthBarb[402] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthBarb[223675] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 8:
                                            dictAvoidanceHealthBarb[3865] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 9:
                                            dictAvoidanceHealthBarb[5212] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 10:
                                            dictAvoidanceHealthBarb[123124] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 11:
                                            dictAvoidanceHealthBarb[123839] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 12:
                                            dictAvoidanceHealthBarb[4103] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 13:
                                            dictAvoidanceHealthBarb[93837] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                    }
                                }
                                break;
                            case "AOEMonkHealth":
                                healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None);
                                for (int i = 1; i <= healthlevels.Length; i++)
                                {
                                    switch (i)
                                    {
                                        case 1:
                                            dictAvoidanceHealthMonk[219702] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthMonk[221225] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 2:
                                            dictAvoidanceHealthMonk[84608] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 3:
                                            dictAvoidanceHealthMonk[4803] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthMonk[4804] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthMonk[224225] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthMonk[247987] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 4:
                                            dictAvoidanceHealthMonk[95868] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 5:
                                            dictAvoidanceHealthMonk[5482] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthMonk[6578] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 6:
                                            dictAvoidanceHealthMonk[108869] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 7:
                                            dictAvoidanceHealthMonk[402] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthMonk[223675] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 8:
                                            dictAvoidanceHealthMonk[3865] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 9:
                                            dictAvoidanceHealthMonk[5212] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 10:
                                            dictAvoidanceHealthMonk[123124] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 11:
                                            dictAvoidanceHealthMonk[123839] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 12:
                                            dictAvoidanceHealthMonk[4103] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 13:
                                            dictAvoidanceHealthMonk[93837] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                    }
                                }
                                break;
                            case "AOEWizardHealth":
                                healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None);
                                for (int i = 1; i <= healthlevels.Length; i++)
                                {
                                    switch (i)
                                    {
                                        case 1:
                                            dictAvoidanceHealthWizard[219702] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWizard[221225] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 2:
                                            dictAvoidanceHealthWizard[84608] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 3:
                                            dictAvoidanceHealthWizard[4803] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWizard[4804] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWizard[224225] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWizard[247987] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 4:
                                            dictAvoidanceHealthWizard[95868] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 5:
                                            dictAvoidanceHealthWizard[5482] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWizard[6578] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 6:
                                            dictAvoidanceHealthWizard[108869] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 7:
                                            dictAvoidanceHealthWizard[402] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWizard[223675] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 8:
                                            dictAvoidanceHealthWizard[3865] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 9:
                                            dictAvoidanceHealthWizard[5212] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 10:
                                            dictAvoidanceHealthWizard[123124] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 11:
                                            dictAvoidanceHealthWizard[123839] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 12:
                                            dictAvoidanceHealthWizard[4103] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 13:
                                            dictAvoidanceHealthWizard[93837] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                    }
                                }
                                break;
                            case "AOEWitchHealth":
                                healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None);
                                for (int i = 1; i <= healthlevels.Length; i++)
                                {
                                    switch (i)
                                    {
                                        case 1:
                                            dictAvoidanceHealthWitch[219702] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWitch[221225] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 2:
                                            dictAvoidanceHealthWitch[84608] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 3:
                                            dictAvoidanceHealthWitch[4803] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWitch[4804] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWitch[224225] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWitch[247987] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 4:
                                            dictAvoidanceHealthWitch[95868] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 5:
                                            dictAvoidanceHealthWitch[5482] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWitch[6578] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 6:
                                            dictAvoidanceHealthWitch[108869] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 7:
                                            dictAvoidanceHealthWitch[402] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthWitch[223675] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 8:
                                            dictAvoidanceHealthWitch[3865] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 9:
                                            dictAvoidanceHealthWitch[5212] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 10:
                                            dictAvoidanceHealthWitch[123124] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 11:
                                            dictAvoidanceHealthWitch[123839] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 12:
                                            dictAvoidanceHealthWitch[4103] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 13:
                                            dictAvoidanceHealthWitch[93837] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                    }
                                }
                                break;
                            case "AOEDemonHealth":
                                healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None);
                                for (int i = 1; i <= healthlevels.Length; i++)
                                {
                                    switch (i)
                                    {
                                        case 1:
                                            dictAvoidanceHealthDemon[219702] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthDemon[221225] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 2:
                                            dictAvoidanceHealthDemon[84608] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 3:
                                            dictAvoidanceHealthDemon[4803] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthDemon[4804] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthDemon[224225] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthDemon[247987] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 4:
                                            dictAvoidanceHealthDemon[95868] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 5:
                                            dictAvoidanceHealthDemon[5482] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthDemon[6578] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 6:
                                            dictAvoidanceHealthDemon[108869] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 7:
                                            dictAvoidanceHealthDemon[402] = Convert.ToDouble(healthlevels[i - 1]);
                                            dictAvoidanceHealthDemon[223675] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 8:
                                            dictAvoidanceHealthDemon[3865] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 9:
                                            dictAvoidanceHealthDemon[5212] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 10:
                                            dictAvoidanceHealthDemon[123124] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 11:
                                            dictAvoidanceHealthDemon[123839] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 12:
                                            dictAvoidanceHealthDemon[4103] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                        case 13:
                                            dictAvoidanceHealthDemon[93837] = Convert.ToDouble(healthlevels[i - 1]);
                                            break;
                                    }
                                }
                                break;
                            case "AOERadius":
                                healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None);
                                for (int i = 1; i <= healthlevels.Length; i++)
                                {
                                    switch (i)
                                    {
                                        case 1:
                                            dictAvoidanceRadius[219702] = Convert.ToInt32(healthlevels[i - 1]);
                                            dictAvoidanceRadius[221225] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 2:
                                            dictAvoidanceRadius[84608] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 3:
                                            dictAvoidanceRadius[4803] = Convert.ToInt32(healthlevels[i - 1]);
                                            dictAvoidanceRadius[4804] = Convert.ToInt32(healthlevels[i - 1]);
                                            dictAvoidanceRadius[224225] = Convert.ToInt32(healthlevels[i - 1]);
                                            dictAvoidanceRadius[247987] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 4:
                                            dictAvoidanceRadius[95868] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 5:
                                            dictAvoidanceRadius[5482] = Convert.ToInt32(healthlevels[i - 1]);
                                            dictAvoidanceRadius[6578] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 6:
                                            dictAvoidanceRadius[108869] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 7:
                                            dictAvoidanceRadius[402] = Convert.ToInt32(healthlevels[i - 1]);
                                            dictAvoidanceRadius[223675] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 8:
                                            dictAvoidanceRadius[3865] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 9:
                                            dictAvoidanceRadius[5212] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 10:
                                            dictAvoidanceRadius[123124] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 11:
                                            dictAvoidanceRadius[123839] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 12:
                                            dictAvoidanceRadius[4103] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                        case 13:
                                            dictAvoidanceRadius[93837] = Convert.ToInt32(healthlevels[i - 1]);
                                            break;
                                    }
                                }
                                break;
                        }
                    }
                }
                configReader.Close();
            }

            if (!File.Exists(sTrinityEmailConfigFile))
            {
                saveEmailConfiguration();
            }
            using (StreamReader configReader = new StreamReader(sTrinityEmailConfigFile))
            {
                while (!configReader.EndOfStream)
                {
                    string[] config = configReader.ReadLine().Split('=');
                    if (config != null)
                    {
                        switch (config[0])
                        {
                            case "EnableEmail":
                                settings.bEnableEmail = Convert.ToBoolean(config[1]);
                                break;
                            case "EmailAddress":
                                sEmailAddress = config[1];
                                break;
                            case "EmailPassword":
                                sEmailPassword = config[1];
                                break;
                            case "BotName":
                                sBotName = config[1];
                                break;
                        }
                    }
                }
                configReader.Close();
            }
            bMappedPlayerAbilities = false;
        }



        // ********************************************
        // *********** CONFIG WINDOW REGION ***********
        // ********************************************
        #region configWindow
        // First we create a variable that is of the "type" of the actual config window item - eg a "RadioButton" for each, well, radiobutton
        // Later on we will "Link" these variables to the ACTUAL items within the XAML file, so we can do things with the XAML stuff
        // I try to match the names of the variables here, with the "Name=" I give the item in the XAML - this isn't necessary, but makes things simpler
        private Button saveButton, defaultButton, testButton, sortButton, resetCombat, resetAOE0, resetAOE1, resetAOE2, resetAOE3, resetAOE4, resetWorld, resetItems, resetTown, resetAdvanced, resetMobile;
        private RadioButton checkTreasureIgnore, checkTreasureNormal, checkTreasurePrioritize, checkTreasureKamikaze, btnRulesGiles, btnRulesCustom, btnSalvage, btnSell, checkIgnoreAll, checkIgnoreNone;
        private CheckBox checkAvoidance, checkGlobes, checkCritical, checkGrave, checkBacktracking, checkCraftTomes, checkDesigns, checkFollower, checkGemEmerald, checkGemAmethyst, checkGemTopaz, checkGemRuby,
            checkIgnoreCorpses, checkMovementAbilities, checkTPS, checkLogStucks, checkUnstucker, checkExtendedRange, checkDebugInfo, checkProwl, checkAndroid, checkSelectiveWW,
            checkWaitWrath, checkGoblinWrath, checkFuryDumpWrath, checkFuryDumpAlways, checkProfileReload, checkMonkInna, checkKiteArchonOnly, checkWaitArchonAzmo, checkWrath90, checkEmail, checkLegendaryNotify;
        private Slider slideTriggerRange, slideWeapon, slideJewelry, slideArmor, slideGoldAmount, slideContainerRange, slideDestructibleRange, slideTPS,
            slideNotifyWeapon, slideNotifyJewelry, slideNotifyArmor, slideLootDelay, slideVaultDelay, slideKite0, slideKite2, slideKite3, slideKite4;
        private TextBox textTriggerRange, JewelryText, ArmorText, WeaponText, JewelryNotifyText, ArmorNotifyText, WeaponNotifyText, textGoldAmount, textContainerRange, textDestructibleRange,
            textTPS, textProwlKey, textAndroidKey, textLootDelay, textVaultDelay, textKite0, textKite2, textKite3, textKite4, txtEmailAddress, txtEmailPassword, txtBotName;
        private ComboBox comboWB, comboWY, comboAB, comboAY, comboJB, comboJY, comboGems, comboMisc, comboPotions, comboPotionLevel, comboLegendary;
        // I used an array of sliders for all the AOE stuff because there were just too many to handle separately, and they all affect the same sort of values
        // So looping through arrays and doing things to them this way meant less code and easier to add more AOE stuff in the future
        private Slider[,] slideAOERadius = new Slider[5, 13];
        private Slider[,] slideAOEHealth = new Slider[5, 13];
        private TextBox[,] textAOERadius = new TextBox[5, 13];
        private TextBox[,] textAOEHealth = new TextBox[5, 13];
        // Sliders for health potions, and health globes, for each class
        private Slider slidePot0, slidePot1, slidePot2, slidePot3, slidePot4, slideGlobe0, slideGlobe1, slideGlobe2, slideGlobe3, slideGlobe4;
        private TextBox textPot0, textPot1, textPot2, textPot3, textPot4, textGlobe0, textGlobe1, textGlobe2, textGlobe3, textGlobe4;

        // This is needed by DB, is essentially the ACTUAL window object itself
        private Window configWindow;

        // This is what "creates" the window
        public Window DisplayWindow
        {
            get
            {
                // Check we can actually find the .xaml file first - if not, report an error
                if (!File.Exists(sTrinityPluginPath + "GilesTrinity.xaml"))
                    Log("ERROR: Can't find \"" + sTrinityPluginPath + "GilesTrinity.xaml\"");
                try
                {
                    if (configWindow == null)
                    {
                        configWindow = new Window();
                    }
                    StreamReader xamlStream = new StreamReader(sTrinityPluginPath + "GilesTrinity.xaml");
                    DependencyObject xamlContent = XamlReader.Load(xamlStream.BaseStream) as DependencyObject;
                    configWindow.Content = xamlContent;

                    // I'm not going to comment everything below - it's all pretty similar
                    // Basically the concept is this:
                    // You take the variable you created above (30 lines up or so), and you use "FindLogicalNode" to sort of "link" the variable, to that object within the XAML file
                    // By using the "Name" tag as the way of finding it

                    // After assigning the variable to the actual node in the XAML, you then need to add event handlers - so we can do things when the user makes changes to those elements
                    // You can also alter settings and values of the nodes - eg the min-max values, the current value etc. - by using the variable we link

                    // Now - the huge list below is because I have so many damned config options of different types!
                    // Note that I do *NOT* have any events on text boxes - because I set all textboxes to uneditable/unchangeable - they are "read only"
                    // I simply use them to show the user what the slider-value is currently set to (so when the slider changes, my code updates the text box)

                    slideWeapon = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideWeaponScore") as Slider;
                    slideWeapon.ValueChanged += trackScoreWeapons_Scroll;
                    slideWeapon.SmallChange = 200;
                    slideWeapon.LargeChange = 1000;
                    slideWeapon.TickFrequency = 2000;
                    slideWeapon.IsSnapToTickEnabled = true;

                    slideJewelry = LogicalTreeHelper.FindLogicalNode(xamlContent, "sliderJewelryScore") as Slider;
                    slideJewelry.ValueChanged += trackScoreJewelry_Scroll;
                    slideJewelry.SmallChange = 100;
                    slideJewelry.LargeChange = 500;
                    slideJewelry.TickFrequency = 1000;
                    slideJewelry.IsSnapToTickEnabled = true;

                    slideArmor = LogicalTreeHelper.FindLogicalNode(xamlContent, "sliderArmorScore") as Slider;
                    slideArmor.ValueChanged += trackScoreArmor_Scroll;
                    slideArmor.SmallChange = 100;
                    slideArmor.LargeChange = 500;
                    slideArmor.TickFrequency = 1000;
                    slideArmor.IsSnapToTickEnabled = true;

                    slideGoldAmount = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGoldAmount") as Slider;
                    slideGoldAmount.ValueChanged += trackGoldAmount_Scroll;
                    slideGoldAmount.SmallChange = 5;
                    slideGoldAmount.LargeChange = 20;
                    slideGoldAmount.TickFrequency = 50;
                    slideGoldAmount.IsSnapToTickEnabled = true;

                    slideNotifyWeapon = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideWeaponNotifyScore") as Slider;
                    slideNotifyWeapon.ValueChanged += trackNotifyWeapons_Scroll;
                    slideNotifyWeapon.SmallChange = 200;
                    slideNotifyWeapon.LargeChange = 1000;
                    slideNotifyWeapon.TickFrequency = 2000;
                    slideNotifyWeapon.IsSnapToTickEnabled = true;

                    slideNotifyJewelry = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideJewelryNotifyScore") as Slider;
                    slideNotifyJewelry.ValueChanged += trackNotifyJewelry_Scroll;
                    slideNotifyJewelry.SmallChange = 100;
                    slideNotifyJewelry.LargeChange = 500;
                    slideNotifyJewelry.TickFrequency = 1000;
                    slideNotifyJewelry.IsSnapToTickEnabled = true;

                    slideNotifyArmor = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideArmorNotifyScore") as Slider;
                    slideNotifyArmor.ValueChanged += trackNotifyArmor_Scroll;
                    slideNotifyArmor.SmallChange = 100;
                    slideNotifyArmor.LargeChange = 500;
                    slideNotifyArmor.TickFrequency = 1000;
                    slideNotifyArmor.IsSnapToTickEnabled = true;

                    textGoldAmount = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGoldAmount") as TextBox;

                    JewelryText = LogicalTreeHelper.FindLogicalNode(xamlContent, "JewelryScore") as TextBox;
                    ArmorText = LogicalTreeHelper.FindLogicalNode(xamlContent, "ArmorScore") as TextBox;
                    WeaponText = LogicalTreeHelper.FindLogicalNode(xamlContent, "WeaponScore") as TextBox;

                    JewelryNotifyText = LogicalTreeHelper.FindLogicalNode(xamlContent, "JewelryNotifyScore") as TextBox;
                    ArmorNotifyText = LogicalTreeHelper.FindLogicalNode(xamlContent, "ArmorNotifyScore") as TextBox;
                    WeaponNotifyText = LogicalTreeHelper.FindLogicalNode(xamlContent, "WeaponNotifyScore") as TextBox;

                    comboWB = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboWB") as ComboBox;
                    comboWB.SelectionChanged += comboWB_changed;
                    comboWY = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboWY") as ComboBox;
                    comboWY.SelectionChanged += comboWY_changed;
                    comboAB = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboAB") as ComboBox;
                    comboAB.SelectionChanged += comboAB_changed;
                    comboAY = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboAY") as ComboBox;
                    comboAY.SelectionChanged += comboAY_changed;
                    comboJB = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboJB") as ComboBox;
                    comboJB.SelectionChanged += comboJB_changed;
                    comboJY = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboJY") as ComboBox;
                    comboJY.SelectionChanged += comboJY_changed;
                    comboGems = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboGems") as ComboBox;
                    comboGems.SelectionChanged += comboGems_changed;
                    comboMisc = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboMisc") as ComboBox;
                    comboMisc.SelectionChanged += comboMisc_changed;
                    comboPotions = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboPotions") as ComboBox;
                    comboPotions.SelectionChanged += comboPotions_changed;
                    comboPotionLevel = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboPotionLevel") as ComboBox;
                    comboPotionLevel.SelectionChanged += comboPotionLevel_changed;

                    comboLegendary = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboLegendary") as ComboBox;
                    comboLegendary.SelectionChanged += comboLegendary_changed;

                    checkCraftTomes = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkCraftTomes") as CheckBox;
                    checkCraftTomes.Checked += checkCraftTomes_check;
                    checkCraftTomes.Unchecked += checkCraftTomes_uncheck;

                    checkDesigns = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkDesigns") as CheckBox;
                    checkDesigns.Checked += checkDesigns_check;
                    checkDesigns.Unchecked += checkDesigns_uncheck;

                    checkFollower = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkFollower") as CheckBox;
                    checkFollower.Checked += checkFollower_check;
                    checkFollower.Unchecked += checkFollower_uncheck;

                    checkGemEmerald = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGemEmerald") as CheckBox;
                    checkGemEmerald.Checked += checkEmerald_check;
                    checkGemEmerald.Unchecked += checkEmerald_uncheck;

                    checkGemTopaz = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGemTopaz") as CheckBox;
                    checkGemTopaz.Checked += checkTopaz_check;
                    checkGemTopaz.Unchecked += checkTopaz_uncheck;

                    checkGemAmethyst = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGemAmethyst") as CheckBox;
                    checkGemAmethyst.Checked += checkAmethyst_check;
                    checkGemAmethyst.Unchecked += checkAmethyst_uncheck;

                    checkGemRuby = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGemRuby") as CheckBox;
                    checkGemRuby.Checked += checkRuby_check;
                    checkGemRuby.Unchecked += checkRuby_uncheck;

                    checkMovementAbilities = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkMovementAbilities") as CheckBox;
                    checkMovementAbilities.Checked += checkMovementAbilities_check;
                    checkMovementAbilities.Unchecked += checkMovementAbilities_uncheck;


                    btnRulesGiles = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnRulesGiles") as RadioButton;
                    btnRulesGiles.Checked += btnRulesGiles_check;
                    btnRulesCustom = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnRulesCustom") as RadioButton;
                    btnRulesCustom.Checked += btnRulesCustom_check;

                    btnSalvage = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnSalvage") as RadioButton;
                    btnSalvage.Checked += btnSalvage_check;
                    btnSell = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnSell") as RadioButton;
                    btnSell.Checked += btnSell_check;

                    testButton = LogicalTreeHelper.FindLogicalNode(xamlContent, "buttonTest") as Button;
                    testButton.Click += buttonTest_Click;

                    sortButton = LogicalTreeHelper.FindLogicalNode(xamlContent, "buttonSort") as Button;
                    sortButton.Click += buttonSort_Click;

                    checkTreasureIgnore = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnTreasureIgnore") as RadioButton;
                    checkTreasureIgnore.Checked += checkTreasureIgnore_check;
                    checkTreasureNormal = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnTreasureNormal") as RadioButton;
                    checkTreasureNormal.Checked += checkTreasureNormal_check;
                    checkTreasurePrioritize = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnTreasurePrioritize") as RadioButton;
                    checkTreasurePrioritize.Checked += checkTreasurePrioritize_check;
                    checkTreasureKamikaze = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnTreasureKamikaze") as RadioButton;
                    checkTreasureKamikaze.Checked += checkTreasureKamikaze_check;

                    checkDebugInfo = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkDebugInfo") as CheckBox;
                    checkDebugInfo.Checked += checkDebugInfo_check;
                    checkDebugInfo.Unchecked += checkDebugInfo_uncheck;

                    // prowl stuff
                    checkProwl = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkProwl") as CheckBox;
                    checkProwl.Checked += checkProwl_check;
                    checkProwl.Unchecked += checkProwl_uncheck;
                    textProwlKey = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtProwlAPI") as TextBox;
                    textProwlKey.TextChanged += textProwl_change;
                    checkLegendaryNotify = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkLegendaryNotify") as CheckBox;
                    checkLegendaryNotify.Checked += checkLegendaryNotifyScore_check;
                    checkLegendaryNotify.Unchecked += checkLegendaryNotifyScore_uncheck;

                    //Email stuff
                    checkEmail = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkEmail") as CheckBox;
                    checkEmail.Checked += checkEmail_check;
                    checkEmail.Unchecked += checkEmail_uncheck;
                    txtEmailAddress = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtEmailAddress") as TextBox;
                    txtEmailAddress.TextChanged += txtEmailAddress_change;
                    txtEmailPassword = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtEmailPassword") as TextBox;
                    txtEmailPassword.TextChanged += txtEmailPassword_change;
                    txtBotName = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtBotName") as TextBox;
                    txtBotName.TextChanged += txtBotName_change;

                    // android stuff
                    checkAndroid = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkAndroid") as CheckBox;
                    checkAndroid.Checked += checkAndroid_check;
                    checkAndroid.Unchecked += checkAndroid_uncheck;
                    textAndroidKey = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtAndroidAPI") as TextBox;
                    textAndroidKey.TextChanged += textAndroid_change;

                    checkTPS = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkTPS") as CheckBox;
                    checkTPS.Checked += checkTPS_check;
                    checkTPS.Unchecked += checkTPS_uncheck;
                    textTPS = LogicalTreeHelper.FindLogicalNode(xamlContent, "textTPS") as TextBox;
                    slideTPS = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideTPS") as Slider;
                    slideTPS.ValueChanged += trackTPS_Scroll;
                    slideTPS.SmallChange = 1;
                    slideTPS.LargeChange = 1;
                    slideTPS.TickFrequency = 5;
                    slideTPS.IsSnapToTickEnabled = false;

                    checkLogStucks = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkLogStucks") as CheckBox;
                    checkLogStucks.Checked += checkLogStucks_check;
                    checkLogStucks.Unchecked += checkLogStucks_uncheck;

                    checkUnstucker = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkUnstucker") as CheckBox;
                    checkUnstucker.Checked += checkUnstucker_check;
                    checkUnstucker.Unchecked += checkUnstucker_uncheck;


                    checkProfileReload = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkProfileReload") as CheckBox;
                    checkProfileReload.Checked += checkProfileReload_check;
                    checkProfileReload.Unchecked += checkProfileReload_uncheck;

                    checkExtendedRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkExtendedRange") as CheckBox;
                    checkExtendedRange.Checked += checkExtendedRange_check;
                    checkExtendedRange.Unchecked += checkExtendedRange_uncheck;

                    checkSelectiveWW = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkSelectiveWW") as CheckBox;
                    checkSelectiveWW.Checked += checkSelectiveWW_check;
                    checkSelectiveWW.Unchecked += checkSelectiveWW_uncheck;

                    checkWrath90 = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkWrath90") as CheckBox;
                    checkWrath90.Checked += checkWrath90_check;
                    checkWrath90.Unchecked += checkWrath90_uncheck;

                    checkKiteArchonOnly = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkKiteArchonOnly") as CheckBox;
                    checkKiteArchonOnly.Checked += checkKiteArchonOnly_check;
                    checkKiteArchonOnly.Unchecked += checkKiteArchonOnly_uncheck;

                    checkWaitArchonAzmo = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkWaitArchonAzmo") as CheckBox;
                    checkWaitArchonAzmo.Checked += checkWaitArchonAzmo_check;
                    checkWaitArchonAzmo.Unchecked += checkWaitArchonAzmo_uncheck;

                    checkWaitWrath = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkWaitWrath") as CheckBox;
                    checkWaitWrath.Checked += checkWaitWrath_check;
                    checkWaitWrath.Unchecked += checkWaitWrath_uncheck;
                    checkGoblinWrath = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGoblinWrath") as CheckBox;
                    checkGoblinWrath.Checked += checkGoblinWrath_check;
                    checkGoblinWrath.Unchecked += checkGoblinWrath_uncheck;
                    checkFuryDumpWrath = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkFuryDumpWrath") as CheckBox;
                    checkFuryDumpWrath.Checked += checkFuryDumpWrath_check;
                    checkFuryDumpWrath.Unchecked += checkFuryDumpWrath_uncheck;
                    checkFuryDumpAlways = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkFuryDumpAlways") as CheckBox;
                    checkFuryDumpAlways.Checked += checkFuryDumpAlways_check;
                    checkFuryDumpAlways.Unchecked += checkFuryDumpAlways_uncheck;

                    checkMonkInna = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkMonkInna") as CheckBox;
                    checkMonkInna.Checked += checkMonkInna_check;
                    checkMonkInna.Unchecked += checkMonkInna_uncheck;

                    checkBacktracking = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkBacktracking") as CheckBox;
                    checkBacktracking.Checked += checkBacktracking_check;
                    checkBacktracking.Unchecked += checkBacktracking_uncheck;
                    checkAvoidance = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkAvoidance") as CheckBox;
                    checkAvoidance.Checked += checkAvoidance_check;
                    checkAvoidance.Unchecked += checkAvoidance_uncheck;
                    checkGlobes = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGlobes") as CheckBox;
                    checkGlobes.Checked += checkGlobes_check;
                    checkGlobes.Unchecked += checkGlobes_uncheck;
                    checkCritical = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkCritical") as CheckBox;
                    checkCritical.Checked += checkCritical_check;
                    checkCritical.Unchecked += checkCritical_uncheck;
                    checkGrave = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGrave") as CheckBox;
                    checkGrave.Checked += checkCritical_check;
                    checkGrave.Unchecked += checkCritical_uncheck;

                    textTriggerRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "textTriggerRange") as TextBox;

                    slideTriggerRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideTriggerRange") as Slider;
                    slideTriggerRange.ValueChanged += trackTriggerRange_Scroll;
                    slideTriggerRange.SmallChange = 1;
                    slideTriggerRange.LargeChange = 1;
                    slideTriggerRange.TickFrequency = 5;
                    slideTriggerRange.IsSnapToTickEnabled = false;

                    slideLootDelay = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideLootDelay") as Slider;
                    slideLootDelay.ValueChanged += slideLootDelay_Scroll;
                    slideLootDelay.SmallChange = 100;
                    slideLootDelay.LargeChange = 100;
                    slideLootDelay.TickFrequency = 100;
                    slideLootDelay.IsSnapToTickEnabled = true;
                    textLootDelay = LogicalTreeHelper.FindLogicalNode(xamlContent, "textLootDelay") as TextBox;

                    slideVaultDelay = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideVaultDelay") as Slider;
                    slideVaultDelay.ValueChanged += slideVaultDelay_Scroll;
                    slideVaultDelay.SmallChange = 100;
                    slideVaultDelay.LargeChange = 100;
                    slideVaultDelay.TickFrequency = 100;
                    slideVaultDelay.IsSnapToTickEnabled = true;
                    textVaultDelay = LogicalTreeHelper.FindLogicalNode(xamlContent, "textVaultDelay") as TextBox;

                    slideKite0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideKite0") as Slider;
                    slideKite0.ValueChanged += slideKite0_Scroll;
                    slideKite0.IsSnapToTickEnabled = true;
                    textKite0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textKite0") as TextBox;

                    slideKite2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideKite2") as Slider;
                    slideKite2.ValueChanged += slideKite2_Scroll;
                    slideKite2.IsSnapToTickEnabled = true;
                    textKite2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textKite2") as TextBox;

                    slideKite3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideKite3") as Slider;
                    slideKite3.ValueChanged += slideKite3_Scroll;
                    slideKite3.IsSnapToTickEnabled = true;
                    textKite3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textKite3") as TextBox;

                    slideKite4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideKite4") as Slider;
                    slideKite4.ValueChanged += slideKite4_Scroll;
                    slideKite4.IsSnapToTickEnabled = true;
                    textKite4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textKite4") as TextBox;


                    checkIgnoreAll = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnIgnoreAll") as RadioButton;
                    checkIgnoreAll.Checked += checkIgnoreAll_check;
                    checkIgnoreNone = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnIgnoreNone") as RadioButton;
                    checkIgnoreNone.Checked += checkIgnoreNone_check;

                    checkIgnoreCorpses = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkIgnoreCorpses") as CheckBox;
                    checkIgnoreCorpses.Checked += checkIgnoreCorpses_check;
                    checkIgnoreCorpses.Unchecked += checkIgnoreCorpses_uncheck;

                    textContainerRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "textContainerRange") as TextBox;
                    textDestructibleRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "textDestructibleRange") as TextBox;

                    slideContainerRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideContainerRange") as Slider;
                    slideContainerRange.ValueChanged += trackContainerRange_Scroll;
                    slideContainerRange.SmallChange = 1;
                    slideContainerRange.LargeChange = 1;
                    slideContainerRange.TickFrequency = 5;
                    slideContainerRange.IsSnapToTickEnabled = false;
                    slideDestructibleRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideDestructibleRange") as Slider;
                    slideDestructibleRange.ValueChanged += trackDestructibleRange_Scroll;
                    slideDestructibleRange.SmallChange = 1;
                    slideDestructibleRange.LargeChange = 1;
                    slideDestructibleRange.TickFrequency = 5;
                    slideDestructibleRange.IsSnapToTickEnabled = false;

                    // Globe & pot sliders
                    slidePot0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot0") as Slider;
                    slidePot0.ValueChanged += slidePot0_Scroll;
                    slidePot1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot1") as Slider;
                    slidePot1.ValueChanged += slidePot1_Scroll;
                    slidePot2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot2") as Slider;
                    slidePot2.ValueChanged += slidePot2_Scroll;
                    slidePot3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot3") as Slider;
                    slidePot3.ValueChanged += slidePot3_Scroll;
                    slidePot4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot4") as Slider;
                    slidePot4.ValueChanged += slidePot4_Scroll;
                    slideGlobe0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe0") as Slider;
                    slideGlobe0.ValueChanged += slideGlobe0_Scroll;
                    slideGlobe1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe1") as Slider;
                    slideGlobe1.ValueChanged += slideGlobe1_Scroll;
                    slideGlobe2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe2") as Slider;
                    slideGlobe2.ValueChanged += slideGlobe2_Scroll;
                    slideGlobe3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe3") as Slider;
                    slideGlobe3.ValueChanged += slideGlobe3_Scroll;
                    slideGlobe4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe4") as Slider;
                    slideGlobe4.ValueChanged += slideGlobe4_Scroll;
                    textPot0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot0") as TextBox;
                    textPot1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot1") as TextBox;
                    textPot2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot2") as TextBox;
                    textPot3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot3") as TextBox;
                    textPot4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot4") as TextBox;
                    textGlobe0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe0") as TextBox;
                    textGlobe1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe1") as TextBox;
                    textGlobe2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe2") as TextBox;
                    textGlobe3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe3") as TextBox;
                    textGlobe4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe4") as TextBox;

                    // See how much less code for loops have!? The following links up to, and assigns events to, the 100 or so slider bars and textboxes for AOE config stuff!
                    for (int i = 0; i <= 4; i++)
                    {
                        for (int n = 0; n <= 12; n++)
                        {
                            string sThisNode;

                            sThisNode = "slideRadius" + i.ToString() + "_" + (n + 1).ToString();
                            slideAOERadius[i, n] = LogicalTreeHelper.FindLogicalNode(xamlContent, sThisNode) as Slider;

                            sThisNode = "slideHealth" + i.ToString() + "_" + (n + 1).ToString();
                            slideAOEHealth[i, n] = LogicalTreeHelper.FindLogicalNode(xamlContent, sThisNode) as Slider;

                            sThisNode = "textRadius" + i.ToString() + "_" + (n + 1).ToString();
                            textAOERadius[i, n] = LogicalTreeHelper.FindLogicalNode(xamlContent, sThisNode) as TextBox;

                            sThisNode = "textHealth" + i.ToString() + "_" + (n + 1).ToString();
                            textAOEHealth[i, n] = LogicalTreeHelper.FindLogicalNode(xamlContent, sThisNode) as TextBox;

                            slideAOERadius[i, n].ValueChanged += trackAOERadius_Scroll;
                            slideAOEHealth[i, n].ValueChanged += trackAOEHealth_Scroll;
                            slideAOERadius[i, n].SmallChange = 1;
                            slideAOERadius[i, n].LargeChange = 1;
                            slideAOERadius[i, n].TickFrequency = 1;
                            slideAOERadius[i, n].IsSnapToTickEnabled = true;
                            slideAOEHealth[i, n].SmallChange = 1;
                            slideAOEHealth[i, n].LargeChange = 1;
                            slideAOEHealth[i, n].TickFrequency = 1;
                            slideAOEHealth[i, n].IsSnapToTickEnabled = true;
                        }
                    }

                    // Finally the "defaults" button, and the save config button

                    defaultButton = LogicalTreeHelper.FindLogicalNode(xamlContent, "buttonDefaults") as Button;
                    defaultButton.Click += buttonDefaults_Click;

                    resetCombat = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetCombat") as Button;
                    resetCombat.Click += resetCombat_Click;

                    resetAOE0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE0") as Button;
                    resetAOE0.Click += resetAOE0_Click;

                    resetAOE1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE1") as Button;
                    resetAOE1.Click += resetAOE1_Click;

                    resetAOE2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE2") as Button;
                    resetAOE2.Click += resetAOE2_Click;

                    resetAOE3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE3") as Button;
                    resetAOE3.Click += resetAOE3_Click;

                    resetAOE4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE4") as Button;
                    resetAOE4.Click += resetAOE4_Click;

                    resetWorld = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetWorld") as Button;
                    resetWorld.Click += resetWorld_Click;

                    resetItems = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetItems") as Button;
                    resetItems.Click += resetItems_Click;

                    resetTown = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetTown") as Button;
                    resetTown.Click += resetTown_Click;

                    resetAdvanced = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAdvanced") as Button;
                    resetAdvanced.Click += resetAdvanced_Click;

                    resetMobile = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetMobile") as Button;
                    resetMobile.Click += resetMobile_Click;

                    saveButton = LogicalTreeHelper.FindLogicalNode(xamlContent, "buttonSave") as Button;
                    saveButton.Click += buttonSave_Click;

                    UserControl mainControl = LogicalTreeHelper.FindLogicalNode(xamlContent, "mainControl") as UserControl;
                    // Set height and width and window title of main window
                    configWindow.Height = mainControl.Height + 30;
                    configWindow.Width = mainControl.Width;
                    configWindow.Title = "Giles Trinity";

                    // Event handling for the config window loading up/closing
                    configWindow.Loaded += configWindow_Loaded;
                    configWindow.Closed += configWindow_Closed;

                    // And finally put all of this content in effect
                    configWindow.Content = xamlContent;
                }
                catch (XamlParseException ex)
                {
                    // Log specific XAML exceptions that might have happened above
                    Log(ex.ToString());
                }
                catch (Exception ex)
                {
                    // Log any other issues
                    Log(ex.ToString());
                }
                return configWindow;
            }
        }
        // The below are all event handlers for all the window-elements within the config window
        // WARNING: If you use code to alter the value of something that has an event attached...
        // For example a slider - then your code automatically also fires the event for that slider
        // I use "suppresseventchanges" to make sure that event code ONLY gets called from the USER changing values
        // And NOT from my own code trying to change values
        private static bool bSuppressEventChanges = false;
        // And now the start of all the events, starting with the largest ones - the AOE stuff - which has to figure out WHICH slider you changed
        // And then change the appropriate variable(s)
        private void trackAOEHealth_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            Slider thisslider = sender as Slider;
            string sSliderName = thisslider.Name.Substring(11);
            int iClass = Convert.ToInt32(sSliderName.Substring(0, 1));
            int iAvoid = Convert.ToInt32(sSliderName.Substring(2));
            double dThisHealthLimit = (Math.Round(thisslider.Value) / 100);
            textAOEHealth[iClass, iAvoid - 1].Text = (dThisHealthLimit * 100).ToString();
            switch (iClass)
            {
                case 0:
                    // Barbs
                    switch (iAvoid)
                    {
                        case 1:
                            dictAvoidanceHealthBarb[219702] = dThisHealthLimit;
                            dictAvoidanceHealthBarb[221225] = dThisHealthLimit;
                            break;
                        case 2:
                            dictAvoidanceHealthBarb[84608] = dThisHealthLimit;
                            break;
                        case 3:
                            dictAvoidanceHealthBarb[4803] = dThisHealthLimit;
                            dictAvoidanceHealthBarb[4804] = dThisHealthLimit;
                            dictAvoidanceHealthBarb[224225] = dThisHealthLimit;
                            dictAvoidanceHealthBarb[247987] = dThisHealthLimit;
                            break;
                        case 4:
                            dictAvoidanceHealthBarb[95868] = dThisHealthLimit;
                            break;
                        case 5:
                            dictAvoidanceHealthBarb[5482] = dThisHealthLimit;
                            dictAvoidanceHealthBarb[6578] = dThisHealthLimit;
                            break;
                        case 6:
                            dictAvoidanceHealthBarb[108869] = dThisHealthLimit;
                            break;
                        case 7:
                            dictAvoidanceHealthBarb[402] = dThisHealthLimit;
                            dictAvoidanceHealthBarb[223675] = dThisHealthLimit;
                            break;
                        case 8:
                            dictAvoidanceHealthBarb[3865] = dThisHealthLimit;
                            break;
                        case 9:
                            dictAvoidanceHealthBarb[5212] = dThisHealthLimit;
                            break;
                        case 10:
                            dictAvoidanceHealthBarb[123124] = dThisHealthLimit;
                            break;
                        case 11:
                            dictAvoidanceHealthBarb[123839] = dThisHealthLimit;
                            break;
                        case 12:
                            dictAvoidanceHealthBarb[4103] = dThisHealthLimit;
                            break;
                        case 13:
                            dictAvoidanceHealthBarb[93837] = dThisHealthLimit;
                            break;
                    }
                    break;
                case 1:
                    // Monks
                    switch (iAvoid)
                    {
                        case 1:
                            dictAvoidanceHealthMonk[219702] = dThisHealthLimit;
                            dictAvoidanceHealthMonk[221225] = dThisHealthLimit;
                            break;
                        case 2:
                            dictAvoidanceHealthMonk[84608] = dThisHealthLimit;
                            break;
                        case 3:
                            dictAvoidanceHealthMonk[4803] = dThisHealthLimit;
                            dictAvoidanceHealthMonk[4804] = dThisHealthLimit;
                            dictAvoidanceHealthMonk[224225] = dThisHealthLimit;
                            dictAvoidanceHealthMonk[247987] = dThisHealthLimit;
                            break;
                        case 4:
                            dictAvoidanceHealthMonk[95868] = dThisHealthLimit;
                            break;
                        case 5:
                            dictAvoidanceHealthMonk[5482] = dThisHealthLimit;
                            dictAvoidanceHealthMonk[6578] = dThisHealthLimit;
                            break;
                        case 6:
                            dictAvoidanceHealthMonk[108869] = dThisHealthLimit;
                            break;
                        case 7:
                            dictAvoidanceHealthMonk[402] = dThisHealthLimit;
                            dictAvoidanceHealthMonk[223675] = dThisHealthLimit;
                            break;
                        case 8:
                            dictAvoidanceHealthMonk[3865] = dThisHealthLimit;
                            break;
                        case 9:
                            dictAvoidanceHealthMonk[5212] = dThisHealthLimit;
                            break;
                        case 10:
                            dictAvoidanceHealthMonk[123124] = dThisHealthLimit;
                            break;
                        case 11:
                            dictAvoidanceHealthMonk[123839] = dThisHealthLimit;
                            break;
                        case 12:
                            dictAvoidanceHealthMonk[4103] = dThisHealthLimit;
                            break;
                        case 13:
                            dictAvoidanceHealthMonk[93837] = dThisHealthLimit;
                            break;
                    }
                    break;
                case 2:
                    // Wizards
                    switch (iAvoid)
                    {
                        case 1:
                            dictAvoidanceHealthWizard[219702] = dThisHealthLimit;
                            dictAvoidanceHealthWizard[221225] = dThisHealthLimit;
                            break;
                        case 2:
                            dictAvoidanceHealthWizard[84608] = dThisHealthLimit;
                            break;
                        case 3:
                            dictAvoidanceHealthWizard[4803] = dThisHealthLimit;
                            dictAvoidanceHealthWizard[4804] = dThisHealthLimit;
                            dictAvoidanceHealthWizard[224225] = dThisHealthLimit;
                            dictAvoidanceHealthWizard[247987] = dThisHealthLimit;
                            break;
                        case 4:
                            dictAvoidanceHealthWizard[95868] = dThisHealthLimit;
                            break;
                        case 5:
                            dictAvoidanceHealthWizard[5482] = dThisHealthLimit;
                            dictAvoidanceHealthWizard[6578] = dThisHealthLimit;
                            break;
                        case 6:
                            dictAvoidanceHealthWizard[108869] = dThisHealthLimit;
                            break;
                        case 7:
                            dictAvoidanceHealthWizard[402] = dThisHealthLimit;
                            dictAvoidanceHealthWizard[223675] = dThisHealthLimit;
                            break;
                        case 8:
                            dictAvoidanceHealthWizard[3865] = dThisHealthLimit;
                            break;
                        case 9:
                            dictAvoidanceHealthWizard[5212] = dThisHealthLimit;
                            break;
                        case 10:
                            dictAvoidanceHealthWizard[123124] = dThisHealthLimit;
                            break;
                        case 11:
                            dictAvoidanceHealthWizard[123839] = dThisHealthLimit;
                            break;
                        case 12:
                            dictAvoidanceHealthWizard[4103] = dThisHealthLimit;
                            break;
                        case 13:
                            dictAvoidanceHealthWizard[93837] = dThisHealthLimit;
                            break;
                    }
                    break;
                case 3:
                    // WD's
                    switch (iAvoid)
                    {
                        case 1:
                            dictAvoidanceHealthWitch[219702] = dThisHealthLimit;
                            dictAvoidanceHealthWitch[221225] = dThisHealthLimit;
                            break;
                        case 2:
                            dictAvoidanceHealthWitch[84608] = dThisHealthLimit;
                            break;
                        case 3:
                            dictAvoidanceHealthWitch[4803] = dThisHealthLimit;
                            dictAvoidanceHealthWitch[4804] = dThisHealthLimit;
                            dictAvoidanceHealthWitch[224225] = dThisHealthLimit;
                            dictAvoidanceHealthWitch[247987] = dThisHealthLimit;
                            break;
                        case 4:
                            dictAvoidanceHealthWitch[95868] = dThisHealthLimit;
                            break;
                        case 5:
                            dictAvoidanceHealthWitch[5482] = dThisHealthLimit;
                            dictAvoidanceHealthWitch[6578] = dThisHealthLimit;
                            dictAvoidanceHealthWitch[224225] = dThisHealthLimit;
                            dictAvoidanceHealthWitch[247987] = dThisHealthLimit;
                            break;
                        case 6:
                            dictAvoidanceHealthWitch[108869] = dThisHealthLimit;
                            break;
                        case 7:
                            dictAvoidanceHealthWitch[402] = dThisHealthLimit;
                            dictAvoidanceHealthWitch[223675] = dThisHealthLimit;
                            break;
                        case 8:
                            dictAvoidanceHealthWitch[3865] = dThisHealthLimit;
                            break;
                        case 9:
                            dictAvoidanceHealthWitch[5212] = dThisHealthLimit;
                            break;
                        case 10:
                            dictAvoidanceHealthWitch[123124] = dThisHealthLimit;
                            break;
                        case 11:
                            dictAvoidanceHealthWitch[123839] = dThisHealthLimit;
                            break;
                        case 12:
                            dictAvoidanceHealthWitch[4103] = dThisHealthLimit;
                            break;
                        case 13:
                            dictAvoidanceHealthWitch[93837] = dThisHealthLimit;
                            break;
                    }
                    break;
                case 4:
                    // DH's
                    switch (iAvoid)
                    {
                        case 1:
                            dictAvoidanceHealthDemon[219702] = dThisHealthLimit;
                            dictAvoidanceHealthDemon[221225] = dThisHealthLimit;
                            break;
                        case 2:
                            dictAvoidanceHealthDemon[84608] = dThisHealthLimit;
                            break;
                        case 3:
                            dictAvoidanceHealthDemon[4803] = dThisHealthLimit;
                            dictAvoidanceHealthDemon[4804] = dThisHealthLimit;
                            dictAvoidanceHealthDemon[224225] = dThisHealthLimit;
                            dictAvoidanceHealthDemon[247987] = dThisHealthLimit;
                            break;
                        case 4:
                            dictAvoidanceHealthDemon[95868] = dThisHealthLimit;
                            break;
                        case 5:
                            dictAvoidanceHealthDemon[5482] = dThisHealthLimit;
                            dictAvoidanceHealthDemon[6578] = dThisHealthLimit;
                            break;
                        case 6:
                            dictAvoidanceHealthDemon[108869] = dThisHealthLimit;
                            break;
                        case 7:
                            dictAvoidanceHealthDemon[402] = dThisHealthLimit;
                            dictAvoidanceHealthDemon[223675] = dThisHealthLimit;
                            break;
                        case 8:
                            dictAvoidanceHealthDemon[3865] = dThisHealthLimit;
                            break;
                        case 9:
                            dictAvoidanceHealthDemon[5212] = dThisHealthLimit;
                            break;
                        case 10:
                            dictAvoidanceHealthDemon[123124] = dThisHealthLimit;
                            break;
                        case 11:
                            dictAvoidanceHealthDemon[123839] = dThisHealthLimit;
                            break;
                        case 12:
                            dictAvoidanceHealthDemon[4103] = dThisHealthLimit;
                            break;
                        case 13:
                            dictAvoidanceHealthDemon[93837] = dThisHealthLimit;
                            break;
                    }
                    break;
            }
            bMappedPlayerAbilities = false;
        }
        private void trackAOERadius_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            Slider thisslider = sender as Slider;
            string sSliderName = thisslider.Name.Substring(11);
            int iClass = Convert.ToInt32(sSliderName.Substring(0, 1));
            int iAvoid = Convert.ToInt32(sSliderName.Substring(2));
            int iThisAvoidRadius = (int)Math.Round(thisslider.Value);
            textAOERadius[iClass, iAvoid - 1].Text = iThisAvoidRadius.ToString();
            switch (iAvoid)
            {
                case 1:
                    dictAvoidanceRadius[219702] = iThisAvoidRadius;
                    dictAvoidanceRadius[221225] = iThisAvoidRadius;
                    break;
                case 2:
                    dictAvoidanceRadius[84608] = iThisAvoidRadius;
                    break;
                case 3:
                    dictAvoidanceRadius[4803] = iThisAvoidRadius;
                    dictAvoidanceRadius[4804] = iThisAvoidRadius;
                    dictAvoidanceRadius[224225] = iThisAvoidRadius;
                    dictAvoidanceRadius[247987] = iThisAvoidRadius;
                    break;
                case 4:
                    dictAvoidanceRadius[95868] = iThisAvoidRadius;
                    break;
                case 5:
                    dictAvoidanceRadius[5482] = iThisAvoidRadius;
                    dictAvoidanceRadius[6578] = iThisAvoidRadius;
                    break;
                case 6:
                    dictAvoidanceRadius[108869] = iThisAvoidRadius;
                    break;
                case 7:
                    dictAvoidanceRadius[402] = iThisAvoidRadius;
                    dictAvoidanceRadius[223675] = iThisAvoidRadius;
                    break;
                case 8:
                    dictAvoidanceRadius[3865] = iThisAvoidRadius;
                    break;
                case 9:
                    dictAvoidanceRadius[5212] = iThisAvoidRadius;
                    break;
                case 10:
                    dictAvoidanceRadius[123124] = iThisAvoidRadius;
                    break;
                case 11:
                    dictAvoidanceRadius[123839] = iThisAvoidRadius;
                    break;
                case 12:
                    dictAvoidanceRadius[4103] = iThisAvoidRadius;
                    break;
                case 13:
                    dictAvoidanceRadius[93837] = iThisAvoidRadius;
                    break;
            }
            bool bOldSuppress = bSuppressEventChanges;
            bSuppressEventChanges = true;
            for (int i = 0; i <= 4; i++)
            {
                if (i != iClass)
                {
                    slideAOERadius[i, iAvoid - 1].Value = iThisAvoidRadius;
                    textAOERadius[i, iAvoid - 1].Text = iThisAvoidRadius.ToString();
                }
            }
            bSuppressEventChanges = bOldSuppress;
            bMappedPlayerAbilities = false;
        }

        private void checkIgnoreCorpses_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bIgnoreCorpses = true;
        }
        private void checkIgnoreCorpses_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bIgnoreCorpses = false;
        }
        private void slidePot0_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slidePot0.Value = Math.Round(slidePot0.Value);
            textPot0.Text = slidePot0.Value.ToString();
            settings.dEmergencyHealthPotionBarb = (Math.Round(slidePot0.Value) / 100);
        }
        private void slidePot1_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slidePot1.Value = Math.Round(slidePot1.Value);
            textPot1.Text = slidePot1.Value.ToString();
            settings.dEmergencyHealthPotionMonk = (Math.Round(slidePot1.Value) / 100);
        }
        private void slidePot2_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slidePot2.Value = Math.Round(slidePot2.Value);
            textPot2.Text = slidePot2.Value.ToString();
            settings.dEmergencyHealthPotionWiz = (Math.Round(slidePot2.Value) / 100);
        }
        private void slidePot3_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slidePot3.Value = Math.Round(slidePot3.Value);
            textPot3.Text = slidePot3.Value.ToString();
            settings.dEmergencyHealthPotionWitch = (Math.Round(slidePot3.Value) / 100);
        }
        private void slidePot4_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slidePot4.Value = Math.Round(slidePot4.Value);
            textPot4.Text = slidePot4.Value.ToString();
            settings.dEmergencyHealthPotionDemon = (Math.Round(slidePot4.Value) / 100);
        }
        private void slideGlobe0_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideGlobe0.Value = Math.Round(slideGlobe0.Value);
            textGlobe0.Text = slideGlobe0.Value.ToString();
            settings.dEmergencyHealthGlobeBarb = (Math.Round(slideGlobe0.Value) / 100);
        }
        private void slideGlobe1_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideGlobe1.Value = Math.Round(slideGlobe1.Value);
            textGlobe1.Text = slideGlobe1.Value.ToString();
            settings.dEmergencyHealthGlobeMonk = (Math.Round(slideGlobe1.Value) / 100);
        }
        private void slideGlobe2_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideGlobe2.Value = Math.Round(slideGlobe2.Value);
            textGlobe2.Text = slideGlobe2.Value.ToString();
            settings.dEmergencyHealthGlobeWiz = (Math.Round(slideGlobe2.Value) / 100);
        }
        private void slideGlobe3_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideGlobe3.Value = Math.Round(slideGlobe3.Value);
            textGlobe3.Text = slideGlobe3.Value.ToString();
            settings.dEmergencyHealthGlobeWitch = (Math.Round(slideGlobe3.Value) / 100);
        }
        private void slideGlobe4_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideGlobe4.Value = Math.Round(slideGlobe4.Value);
            textGlobe4.Text = slideGlobe4.Value.ToString();
            settings.dEmergencyHealthGlobeDemon = (Math.Round(slideGlobe4.Value) / 100);
        }

        private void trackContainerRange_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideContainerRange.Value = Math.Round(slideContainerRange.Value);
            textContainerRange.Text = slideContainerRange.Value.ToString();
            settings.iContainerOpenRange = Math.Round(slideContainerRange.Value);
        }
        private void trackDestructibleRange_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideDestructibleRange.Value = Math.Round(slideDestructibleRange.Value);
            textDestructibleRange.Text = slideDestructibleRange.Value.ToString();
            settings.iDestructibleAttackRange = Math.Round(slideDestructibleRange.Value);
        }
        private void checkIgnoreAll_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bIgnoreAllShrines = true;
        }
        private void checkIgnoreNone_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bIgnoreAllShrines = false;
        }
        private void trackGoldAmount_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideGoldAmount.Value = Math.Round(slideGoldAmount.Value);
            textGoldAmount.Text = slideGoldAmount.Value.ToString();
            settings.iMinimumGoldStack = Convert.ToInt32(Math.Round(slideGoldAmount.Value));
        }
        private void comboWB_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterBlueWeapons = Convert.ToInt32(comboWB.SelectedValue);
        }
        private void comboLegendary_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterLegendary = Convert.ToInt32(comboLegendary.SelectedValue);
        }
        private void comboWY_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterYellowWeapons = Convert.ToInt32(comboWY.SelectedValue);
        }
        private void comboAB_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterBlueArmor = Convert.ToInt32(comboAB.SelectedValue);
        }
        private void comboAY_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterYellowArmor = Convert.ToInt32(comboAY.SelectedValue);
        }
        private void comboJB_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterBlueJewelry = Convert.ToInt32(comboJB.SelectedValue);
        }
        private void comboJY_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterYellowJewelry = Convert.ToInt32(comboJY.SelectedValue);
        }
        private void comboGems_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterGems = Convert.ToInt32(comboGems.SelectedValue);
        }
        private void comboMisc_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterMisc = Convert.ToInt32(comboMisc.SelectedValue);
        }
        private void comboPotions_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterPotions = Convert.ToInt32(comboPotions.SelectedValue);
        }
        private void comboPotionLevel_changed(object sender, SelectionChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iFilterPotionLevel = Convert.ToInt32(comboPotionLevel.SelectedValue);
        }
        private void checkFollower_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bPickupFollower = true;
        }
        private void checkFollower_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bPickupFollower = false;
        }
        private void checkDesigns_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bPickupPlans = true;
        }
        private void checkDesigns_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bPickupPlans = false;
        }
        private void checkCraftTomes_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bPickupCraftTomes = true;
        }
        private void checkCraftTomes_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bPickupCraftTomes = false;
        }
        private void btnRulesGiles_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bUseGilesFilters = true;
        }
        private void btnRulesCustom_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bUseGilesFilters = false;
        }
        private void btnSalvage_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bSalvageJunk = true;
        }
        private void btnSell_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bSalvageJunk = false;
        }
        private void checkEmerald_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGemsEmerald = true;
        }
        private void checkEmerald_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGemsEmerald = false;
        }
        private void checkAmethyst_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGemsAmethyst = true;
        }
        private void checkAmethyst_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGemsAmethyst = false;
        }
        private void checkTopaz_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGemsTopaz = true;
        }
        private void checkTopaz_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGemsTopaz = false;
        }
        private void checkRuby_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGemsRuby = true;
        }
        private void checkRuby_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGemsRuby = false;
        }
        private void checkAndroid_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableAndroid = true;
        }
        private void checkAndroid_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableAndroid = false;
        }
        private void textAndroid_change(object sender, TextChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            sAndroidAPIKey = textAndroidKey.Text;
        }
        private void checkProwl_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableProwl = true;
        }
        private void checkProwl_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableProwl = false;
        }
        private void textProwl_change(object sender, TextChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            sProwlAPIKey = textProwlKey.Text;
        }
        private void checkEmail_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableEmail = true;
        }
        private void checkEmail_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableEmail = false;
        }
        private void checkLegendaryNotifyScore_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableLegendaryNotifyScore = true;
        }
        private void checkLegendaryNotifyScore_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableLegendaryNotifyScore = false;
        }
        private void txtEmailAddress_change(object sender, TextChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            sEmailAddress = txtEmailAddress.Text;
        }
        private void txtEmailPassword_change(object sender, TextChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            sEmailPassword = txtEmailPassword.Text;
        }
        private void txtBotName_change(object sender, TextChangedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            sBotName = txtBotName.Text;
        }
        private void trackScoreWeapons_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideWeapon.Value = Math.Round(slideWeapon.Value);
            settings.iNeedPointsToKeepWeapon = slideWeapon.Value;
            WeaponText.Text = slideWeapon.Value.ToString();
        }
        private void trackScoreArmor_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideArmor.Value = Math.Round(slideArmor.Value);
            settings.iNeedPointsToKeepArmor = slideArmor.Value;
            ArmorText.Text = slideArmor.Value.ToString();
        }
        private void trackScoreJewelry_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideJewelry.Value = Math.Round(slideJewelry.Value);
            settings.iNeedPointsToKeepJewelry = slideJewelry.Value;
            JewelryText.Text = slideJewelry.Value.ToString();
        }
        private void trackNotifyWeapons_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideNotifyWeapon.Value = Math.Round(slideNotifyWeapon.Value);
            settings.iNeedPointsToNotifyWeapon = slideNotifyWeapon.Value;
            WeaponNotifyText.Text = slideNotifyWeapon.Value.ToString();
        }
        private void trackNotifyArmor_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideNotifyArmor.Value = Math.Round(slideNotifyArmor.Value);
            settings.iNeedPointsToNotifyArmor = slideNotifyArmor.Value;
            ArmorNotifyText.Text = slideNotifyArmor.Value.ToString();
        }
        private void trackNotifyJewelry_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideNotifyJewelry.Value = Math.Round(slideNotifyJewelry.Value);
            settings.iNeedPointsToNotifyJewelry = slideNotifyJewelry.Value;
            JewelryNotifyText.Text = slideNotifyJewelry.Value.ToString();
        }
        private void checkBacktracking_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableBacktracking = true;
        }
        private void checkBacktracking_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableBacktracking = false;
        }
        private void checkDebugInfo_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bDebugInfo = true;
        }
        private void checkDebugInfo_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bDebugInfo = false;
        }
        private void checkTPS_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableTPS = true;
            BotMain.TicksPerSecond = (int)settings.iTPSAmount;
        }
        private void checkTPS_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableTPS = false;
            BotMain.TicksPerSecond = 10;
        }
        private void checkProfileReload_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableProfileReloading = true;
        }
        private void checkProfileReload_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableProfileReloading = false;
        }
        private void checkUnstucker_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableUnstucker = true;
            Navigator.StuckHandler = new GilesStuckHandler();
        }
        private void checkUnstucker_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableUnstucker = false;
            Navigator.StuckHandler = new DefaultStuckHandler();
        }
        private void checkLogStucks_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bLogStucks = true;
        }
        private void checkLogStucks_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bLogStucks = false;
        }
        private void checkExtendedRange_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bExtendedKillRange = true;
        }
        private void checkExtendedRange_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bExtendedKillRange = false;
        }
        private void checkWrath90_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bWrath90Seconds = true;
        }
        private void checkWrath90_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bWrath90Seconds = false;
        }
        private void checkSelectiveWW_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bSelectiveWhirlwind = true;
        }
        private void checkSelectiveWW_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bSelectiveWhirlwind = false;
        }
        private void checkKiteArchonOnly_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bKiteOnlyArchon = true;
        }
        private void checkKiteArchonOnly_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bKiteOnlyArchon = false;
        }
        private void checkWaitArchonAzmo_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bWaitForArchon = true;
        }
        private void checkWaitArchonAzmo_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bWaitForArchon = false;
        }

        private void checkWaitWrath_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bWaitForWrath = true;
        }
        private void checkWaitWrath_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bWaitForWrath = false;
        }
        private void checkGoblinWrath_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGoblinWrath = true;
        }
        private void checkGoblinWrath_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bGoblinWrath = false;
        }
        private void checkMonkInna_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bMonkInnaSet = true;
        }
        private void checkMonkInna_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bMonkInnaSet = false;
        }
        private void checkFuryDumpWrath_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bFuryDumpWrath = true;
        }
        private void checkFuryDumpWrath_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bFuryDumpWrath = false;
        }
        private void checkFuryDumpAlways_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bFuryDumpAlways = true;
        }
        private void checkFuryDumpAlways_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bFuryDumpAlways = false;
        }
        private void trackTPS_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideTPS.Value = Math.Round(slideTPS.Value);
            textTPS.Text = slideTPS.Value.ToString();
            settings.iTPSAmount = Math.Round(slideTPS.Value);
            if (settings.bEnableTPS)
            {
                BotMain.TicksPerSecond = (int)settings.iTPSAmount;
            }
        }
        private void checkAvoidance_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableAvoidance = true;
        }
        private void checkAvoidance_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableAvoidance = false;
        }
        private void checkGlobes_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableGlobes = true;
        }
        private void checkGlobes_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableGlobes = false;
        }
        private void checkCritical_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableCriticalMass = true;
        }
        private void checkCritical_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bEnableCriticalMass = false;
        }
        private void checkMovementAbilities_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bOutOfCombatMovementPowers = true;
        }
        private void checkMovementAbilities_uncheck(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.bOutOfCombatMovementPowers = false;
        }

        private void slideKite0_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideKite0.Value = Math.Round(slideKite0.Value);
            textKite0.Text = slideKite0.Value.ToString();
            settings.iKiteDistanceBarb = (int)Math.Round(slideKite0.Value);
        }
        private void slideKite2_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideKite2.Value = Math.Round(slideKite2.Value);
            textKite2.Text = slideKite2.Value.ToString();
            settings.iKiteDistanceWiz = (int)Math.Round(slideKite2.Value);
        }
        private void slideKite3_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideKite3.Value = Math.Round(slideKite3.Value);
            textKite3.Text = slideKite3.Value.ToString();
            settings.iKiteDistanceWitch = (int)Math.Round(slideKite3.Value);
        }
        private void slideKite4_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideKite4.Value = Math.Round(slideKite4.Value);
            textKite4.Text = slideKite4.Value.ToString();
            settings.iKiteDistanceDemon = (int)Math.Round(slideKite4.Value);
        }
        private void slideVaultDelay_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideVaultDelay.Value = Math.Round(slideVaultDelay.Value);
            textVaultDelay.Text = slideVaultDelay.Value.ToString();
            settings.iDHVaultMovementDelay = (int)Math.Round(slideVaultDelay.Value);
        }
        private void slideLootDelay_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideLootDelay.Value = Math.Round(slideLootDelay.Value);
            textLootDelay.Text = slideLootDelay.Value.ToString();
            settings.iKillLootDelay = (int)Math.Round(slideLootDelay.Value);
        }
        private void trackTriggerRange_Scroll(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSuppressEventChanges)
                return;
            slideTriggerRange.Value = Math.Round(slideTriggerRange.Value);
            textTriggerRange.Text = slideTriggerRange.Value.ToString();
            settings.iMonsterKillRange = Math.Round(slideTriggerRange.Value);
        }
        // The three events for the treasure goblin priority choice
        private void checkTreasureIgnore_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iTreasureGoblinPriority = 0;
        }
        private void checkTreasureNormal_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iTreasureGoblinPriority = 1;
        }
        private void checkTreasurePrioritize_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iTreasureGoblinPriority = 2;
        }
        private void checkTreasureKamikaze_check(object sender, RoutedEventArgs e)
        {
            if (bSuppressEventChanges)
                return;
            settings.iTreasureGoblinPriority = 3;
        }

        // Event handler for the config window being closed
        private void configWindow_Closed(object sender, EventArgs e)
        {
            configWindow = null;
        }
        // Event handler for the config window loading, update all the window elements!
        private void configWindow_Loaded(object sender, RoutedEventArgs e)
        {
            settingsWindowResetValues();
        }
        // Button-clicked for testing backpack stash-replacer scores
        private void buttonTest_Click(object sender, RoutedEventArgs e)
        {
            TestScoring();
            configWindow.Close();
        }
        // Button-clicked for testing backpack stash-replacer scores
        private void buttonSort_Click(object sender, RoutedEventArgs e)
        {
            configWindow.Close();
            SortStash();
        }
        // Button-clicked for saving the config
        private void buttonSave_Click(object sender, RoutedEventArgs e)
        {
            SaveConfiguration();
            configWindow.Close();
        }
        // Default button clicked
        private void buttonDefaults_Click(object sender, RoutedEventArgs e)
        {
            settings = new GilesSettings();
            dictAvoidanceHealthBarb = new Dictionary<int, double>(dictAvoidanceHealthBarbDefaults);
            dictAvoidanceHealthMonk = new Dictionary<int, double>(dictAvoidanceHealthMonkDefaults);
            dictAvoidanceHealthWizard = new Dictionary<int, double>(dictAvoidanceHealthWizardDefaults);
            dictAvoidanceHealthWitch = new Dictionary<int, double>(dictAvoidanceHealthWitchDefaults);
            dictAvoidanceHealthDemon = new Dictionary<int, double>(dictAvoidanceHealthDemonDefaults);
            dictAvoidanceRadius = new Dictionary<int, double>(dictAvoidanceRadiusDefaults);
            settingsWindowResetValues();
        }
        // Individual reset buttons
        private void resetCombat_Click(object sender, RoutedEventArgs e)
        {
            GilesSettings tempsettings = new GilesSettings();
            settings.bEnableBacktracking = tempsettings.bEnableBacktracking;
            settings.bEnableAvoidance = tempsettings.bEnableAvoidance;
            settings.bEnableGlobes = tempsettings.bEnableGlobes;
            settings.bEnableCriticalMass = tempsettings.bEnableCriticalMass;
            settings.iTreasureGoblinPriority = tempsettings.iTreasureGoblinPriority;
            settings.iMonsterKillRange = tempsettings.iMonsterKillRange;
            settings.iKillLootDelay = tempsettings.iKillLootDelay;
            settings.iDHVaultMovementDelay = tempsettings.iDHVaultMovementDelay;
            settings.bMonkInnaSet = tempsettings.bMonkInnaSet;
            settings.bOutOfCombatMovementPowers = tempsettings.bOutOfCombatMovementPowers;
            settings.bExtendedKillRange = tempsettings.bExtendedKillRange;
            settings.bSelectiveWhirlwind = tempsettings.bSelectiveWhirlwind;
            settings.bWrath90Seconds = tempsettings.bWrath90Seconds;
            settings.bWaitForWrath = tempsettings.bWaitForWrath;
            settings.bGoblinWrath = tempsettings.bGoblinWrath;
            settings.bFuryDumpWrath = tempsettings.bFuryDumpWrath;
            settings.bFuryDumpAlways = tempsettings.bFuryDumpAlways;
            settings.bKiteOnlyArchon = tempsettings.bKiteOnlyArchon;
            settings.bWaitForArchon = tempsettings.bWaitForArchon;
            settingsWindowResetValues();
        }
        private void resetAOE0_Click(object sender, RoutedEventArgs e)
        {
            dictAvoidanceHealthBarb = new Dictionary<int, double>(dictAvoidanceHealthBarbDefaults);
            dictAvoidanceRadius = new Dictionary<int, double>(dictAvoidanceRadiusDefaults);
            GilesSettings tempsettings = new GilesSettings();
            settings.dEmergencyHealthPotionBarb = tempsettings.dEmergencyHealthPotionBarb;
            settings.dEmergencyHealthGlobeBarb = tempsettings.dEmergencyHealthGlobeBarb;
            settings.iKiteDistanceBarb = tempsettings.iKiteDistanceBarb;
            settingsWindowResetValues();
        }
        private void resetAOE1_Click(object sender, RoutedEventArgs e)
        {
            dictAvoidanceHealthMonk = new Dictionary<int, double>(dictAvoidanceHealthMonkDefaults);
            dictAvoidanceRadius = new Dictionary<int, double>(dictAvoidanceRadiusDefaults);
            GilesSettings tempsettings = new GilesSettings();
            settings.dEmergencyHealthPotionMonk = tempsettings.dEmergencyHealthPotionMonk;
            settings.dEmergencyHealthGlobeMonk = tempsettings.dEmergencyHealthGlobeMonk;
            settings.bMonkInnaSet = tempsettings.bMonkInnaSet;
            settingsWindowResetValues();
        }
        private void resetAOE2_Click(object sender, RoutedEventArgs e)
        {
            dictAvoidanceHealthWizard = new Dictionary<int, double>(dictAvoidanceHealthWizardDefaults);
            dictAvoidanceRadius = new Dictionary<int, double>(dictAvoidanceRadiusDefaults);
            GilesSettings tempsettings = new GilesSettings();
            settings.dEmergencyHealthPotionWiz = tempsettings.dEmergencyHealthPotionWiz;
            settings.dEmergencyHealthGlobeWiz = tempsettings.dEmergencyHealthGlobeWiz;
            settings.iKiteDistanceWiz = tempsettings.iKiteDistanceWiz;
            settings.bKiteOnlyArchon = tempsettings.bKiteOnlyArchon;
            settings.bWaitForArchon = tempsettings.bWaitForArchon;
            settingsWindowResetValues();
        }
        private void resetAOE3_Click(object sender, RoutedEventArgs e)
        {
            dictAvoidanceHealthWitch = new Dictionary<int, double>(dictAvoidanceHealthWitchDefaults);
            dictAvoidanceRadius = new Dictionary<int, double>(dictAvoidanceRadiusDefaults);
            GilesSettings tempsettings = new GilesSettings();
            settings.dEmergencyHealthPotionWitch = tempsettings.dEmergencyHealthPotionWitch;
            settings.dEmergencyHealthGlobeWitch = tempsettings.dEmergencyHealthGlobeWitch;
            settings.iKiteDistanceWitch = tempsettings.iKiteDistanceWitch;
            settingsWindowResetValues();
        }
        private void resetAOE4_Click(object sender, RoutedEventArgs e)
        {
            dictAvoidanceHealthDemon = new Dictionary<int, double>(dictAvoidanceHealthDemonDefaults);
            dictAvoidanceRadius = new Dictionary<int, double>(dictAvoidanceRadiusDefaults);
            GilesSettings tempsettings = new GilesSettings();
            settings.dEmergencyHealthPotionDemon = tempsettings.dEmergencyHealthPotionDemon;
            settings.dEmergencyHealthGlobeDemon = tempsettings.dEmergencyHealthGlobeDemon;
            settings.iKiteDistanceDemon = tempsettings.iKiteDistanceDemon;
            settingsWindowResetValues();
        }
        private void resetWorld_Click(object sender, RoutedEventArgs e)
        {
            GilesSettings tempsettings = new GilesSettings();
            settings.bIgnoreAllShrines = tempsettings.bIgnoreAllShrines;
            settings.bIgnoreCorpses = tempsettings.bIgnoreCorpses;
            settings.iContainerOpenRange = tempsettings.iContainerOpenRange;
            settings.iDestructibleAttackRange = tempsettings.iDestructibleAttackRange;
            settingsWindowResetValues();
        }
        private void resetItems_Click(object sender, RoutedEventArgs e)
        {
            GilesSettings tempsettings = new GilesSettings();
            settings.bUseGilesFilters = tempsettings.bUseGilesFilters;
            settings.iMinimumGoldStack = tempsettings.iMinimumGoldStack;
            settings.iFilterPotions = tempsettings.iFilterPotions;
            settings.iFilterLegendary = tempsettings.iFilterLegendary;
            settings.iFilterBlueWeapons = tempsettings.iFilterBlueWeapons;
            settings.iFilterYellowWeapons = tempsettings.iFilterYellowWeapons;
            settings.iFilterBlueArmor = tempsettings.iFilterBlueArmor;
            settings.iFilterYellowArmor = tempsettings.iFilterYellowArmor;
            settings.iFilterBlueJewelry = tempsettings.iFilterBlueJewelry;
            settings.iFilterYellowJewelry = tempsettings.iFilterYellowJewelry;
            settings.iFilterGems = tempsettings.iFilterGems;
            settings.iFilterMisc = tempsettings.iFilterMisc;
            settings.iFilterPotionLevel = tempsettings.iFilterPotionLevel;
            settings.bGemsEmerald = tempsettings.bGemsEmerald;
            settings.bGemsAmethyst = tempsettings.bGemsAmethyst;
            settings.bGemsTopaz = tempsettings.bGemsTopaz;
            settings.bGemsRuby = tempsettings.bGemsRuby;
            settings.bPickupCraftTomes = tempsettings.bPickupCraftTomes;
            settings.bPickupPlans = tempsettings.bPickupPlans;
            settings.bPickupFollower = tempsettings.bPickupFollower;
            settingsWindowResetValues();
        }
        private void resetTown_Click(object sender, RoutedEventArgs e)
        {
            GilesSettings tempsettings = new GilesSettings();
            settings.bSalvageJunk = tempsettings.bSalvageJunk;
            settings.iNeedPointsToKeepJewelry = tempsettings.iNeedPointsToKeepJewelry;
            settings.iNeedPointsToKeepArmor = tempsettings.iNeedPointsToKeepArmor;
            settings.iNeedPointsToKeepWeapon = tempsettings.iNeedPointsToKeepWeapon;
            settingsWindowResetValues();
        }
        private void resetAdvanced_Click(object sender, RoutedEventArgs e)
        {
            GilesSettings tempsettings = new GilesSettings();
            settings.bEnableTPS = tempsettings.bEnableTPS;
            settings.iTPSAmount = tempsettings.iTPSAmount;
            settings.bLogStucks = tempsettings.bLogStucks;
            settings.bEnableUnstucker = tempsettings.bEnableUnstucker;
            settings.bEnableProfileReloading = tempsettings.bEnableProfileReloading;
            settings.bDebugInfo = tempsettings.bDebugInfo;
            settingsWindowResetValues();
        }
        private void resetMobile_Click(object sender, RoutedEventArgs e)
        {
            GilesSettings tempsettings = new GilesSettings();
            settings.bEnableProwl = tempsettings.bEnableProwl;
            settings.bEnableAndroid = tempsettings.bEnableAndroid;
            settings.iNeedPointsToNotifyJewelry = tempsettings.iNeedPointsToNotifyJewelry;
            settings.iNeedPointsToNotifyArmor = tempsettings.iNeedPointsToNotifyArmor;
            settings.iNeedPointsToNotifyWeapon = tempsettings.iNeedPointsToNotifyWeapon;
            settings.bEnableEmail = tempsettings.bEnableEmail;
            settings.bEnableLegendaryNotifyScore = tempsettings.bEnableLegendaryNotifyScore;
            settingsWindowResetValues();
        }

        // This function sets all of the window elements of the config window, to the current actual values held in the variables
        private void settingsWindowResetValues()
        {
            bSuppressEventChanges = true;
            for (int i = 0; i <= 4; i++)
            {
                for (int n = 1; n <= 13; n++)
                {
                    switch (n)
                    {
                        case 1:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[219702];
                            break;
                        case 2:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[84608];
                            break;
                        case 3:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[4804];
                            break;
                        case 4:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[95868];
                            break;
                        case 5:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[5482];
                            break;
                        case 6:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[108869];
                            break;
                        case 7:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[223675];
                            break;
                        case 8:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[3865];
                            break;
                        case 9:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[5212];
                            break;
                        case 10:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[123124];
                            break;
                        case 11:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[123839];
                            break;
                        case 12:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[4103];
                            break;
                        case 13:
                            slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[93837];
                            break;
                    }
                    switch (i)
                    {
                        case 0:
                            // barbs
                            switch (n)
                            {
                                case 1:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[219702] * 100;
                                    break;
                                case 2:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[84608] * 100;
                                    break;
                                case 3:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[4804] * 100;
                                    break;
                                case 4:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[95868] * 100;
                                    break;
                                case 5:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[5482] * 100;
                                    break;
                                case 6:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[108869] * 100;
                                    break;
                                case 7:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[223675] * 100;
                                    break;
                                case 8:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[3865] * 100;
                                    break;
                                case 9:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[5212] * 100;
                                    break;
                                case 10:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[123124] * 100;
                                    break;
                                case 11:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[123839] * 100;
                                    break;
                                case 12:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[4103] * 100;
                                    break;
                                case 13:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[93837] * 100;
                                    break;
                            }
                            break;
                        case 1:
                            // Monks
                            switch (n)
                            {
                                case 1:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[219702] * 100;
                                    break;
                                case 2:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[84608] * 100;
                                    break;
                                case 3:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[4804] * 100;
                                    break;
                                case 4:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[95868] * 100;
                                    break;
                                case 5:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[5482] * 100;
                                    break;
                                case 6:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[108869] * 100;
                                    break;
                                case 7:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[223675] * 100;
                                    break;
                                case 8:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[3865] * 100;
                                    break;
                                case 9:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[5212] * 100;
                                    break;
                                case 10:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[123124] * 100;
                                    break;
                                case 11:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[123839] * 100;
                                    break;
                                case 12:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[4103] * 100;
                                    break;
                                case 13:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[93837] * 100;
                                    break;
                            }
                            break;
                        case 2:
                            // Wizards
                            switch (n)
                            {
                                case 1:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[219702] * 100;
                                    break;
                                case 2:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[84608] * 100;
                                    break;
                                case 3:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[4804] * 100;
                                    break;
                                case 4:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[95868] * 100;
                                    break;
                                case 5:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[5482] * 100;
                                    break;
                                case 6:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[108869] * 100;
                                    break;
                                case 7:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[223675] * 100;
                                    break;
                                case 8:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[3865] * 100;
                                    break;
                                case 9:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[5212] * 100;
                                    break;
                                case 10:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[123124] * 100;
                                    break;
                                case 11:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[123839] * 100;
                                    break;
                                case 12:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[4103] * 100;
                                    break;
                                case 13:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[93837] * 100;
                                    break;
                            }
                            break;
                        case 3:
                            // Witch Doctors
                            switch (n)
                            {
                                case 1:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[219702] * 100;
                                    break;
                                case 2:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[84608] * 100;
                                    break;
                                case 3:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[4804] * 100;
                                    break;
                                case 4:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[95868] * 100;
                                    break;
                                case 5:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[5482] * 100;
                                    break;
                                case 6:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[108869] * 100;
                                    break;
                                case 7:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[223675] * 100;
                                    break;
                                case 8:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[3865] * 100;
                                    break;
                                case 9:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[5212] * 100;
                                    break;
                                case 10:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[123124] * 100;
                                    break;
                                case 11:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[123839] * 100;
                                    break;
                                case 12:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[4103] * 100;
                                    break;
                                case 13:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[93837] * 100;
                                    break;
                            }
                            break;
                        case 4:
                            // Demon Hunters
                            switch (n)
                            {
                                case 1:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[219702] * 100;
                                    break;
                                case 2:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[84608] * 100;
                                    break;
                                case 3:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[4804] * 100;
                                    break;
                                case 4:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[95868] * 100;
                                    break;
                                case 5:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[5482] * 100;
                                    break;
                                case 6:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[108869] * 100;
                                    break;
                                case 7:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[223675] * 100;
                                    break;
                                case 8:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[3865] * 100;
                                    break;
                                case 9:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[5212] * 100;
                                    break;
                                case 10:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[123124] * 100;
                                    break;
                                case 11:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[123839] * 100;
                                    break;
                                case 12:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[4103] * 100;
                                    break;
                                case 13:
                                    slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[93837] * 100;
                                    break;
                            }
                            break;
                    } // Switch on i
                    textAOERadius[i, n - 1].Text = slideAOERadius[i, n - 1].Value.ToString();
                    textAOEHealth[i, n - 1].Text = slideAOEHealth[i, n - 1].Value.ToString();
                } // Loop through the avoidances
            } // Loop through the classes

            comboLegendary.SelectedIndex = comboLegendary.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterLegendary.ToString())).Count();
            comboWB.SelectedIndex = comboWB.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterBlueWeapons.ToString())).Count();
            comboWY.SelectedIndex = comboWY.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterYellowWeapons.ToString())).Count();
            comboAB.SelectedIndex = comboAB.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterBlueArmor.ToString())).Count();
            comboAY.SelectedIndex = comboAY.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterYellowArmor.ToString())).Count();
            comboJB.SelectedIndex = comboJB.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterBlueJewelry.ToString())).Count();
            comboJY.SelectedIndex = comboJY.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterYellowJewelry.ToString())).Count();
            comboGems.SelectedIndex = comboGems.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterGems.ToString())).Count();
            comboMisc.SelectedIndex = comboMisc.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterMisc.ToString())).Count();
            comboPotions.SelectedIndex = comboPotions.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterPotions.ToString())).Count();
            comboPotionLevel.SelectedIndex = comboPotionLevel.Items.Cast<ComboBoxItem>().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterPotionLevel.ToString())).Count();
            checkCraftTomes.IsChecked = settings.bPickupCraftTomes;
            checkDesigns.IsChecked = settings.bPickupPlans;
            checkFollower.IsChecked = settings.bPickupFollower;
            checkGemEmerald.IsChecked = settings.bGemsEmerald;
            checkGemAmethyst.IsChecked = settings.bGemsAmethyst;
            checkGemTopaz.IsChecked = settings.bGemsTopaz;
            checkGemRuby.IsChecked = settings.bGemsRuby;
            if (settings.bSalvageJunk)
            {
                btnSalvage.IsChecked = true;
                btnSell.IsChecked = false;
            }
            else
            {
                btnSell.IsChecked = true;
                btnSalvage.IsChecked = false;
            }
            if (settings.bUseGilesFilters)
            {
                btnRulesGiles.IsChecked = true;
                btnRulesCustom.IsChecked = false;
            }
            else
            {
                btnRulesCustom.IsChecked = true;
                btnRulesGiles.IsChecked = false;
            }
            slideWeapon.Value = Math.Round(settings.iNeedPointsToKeepWeapon);
            WeaponText.Text = slideWeapon.Value.ToString();
            slideArmor.Value = Math.Round(settings.iNeedPointsToKeepArmor);
            ArmorText.Text = slideArmor.Value.ToString();
            slideJewelry.Value = Math.Round(settings.iNeedPointsToKeepJewelry);
            JewelryText.Text = slideJewelry.Value.ToString();

            slideNotifyWeapon.Value = Math.Round(settings.iNeedPointsToNotifyWeapon);
            WeaponNotifyText.Text = slideNotifyWeapon.Value.ToString();
            slideNotifyArmor.Value = Math.Round(settings.iNeedPointsToNotifyArmor);
            ArmorNotifyText.Text = slideNotifyArmor.Value.ToString();
            slideNotifyJewelry.Value = Math.Round(settings.iNeedPointsToNotifyJewelry);
            JewelryNotifyText.Text = slideNotifyJewelry.Value.ToString();

            slideGoldAmount.Value = settings.iMinimumGoldStack;
            textGoldAmount.Text = settings.iMinimumGoldStack.ToString();
            slideTriggerRange.Value = settings.iMonsterKillRange;
            textTriggerRange.Text = settings.iMonsterKillRange.ToString();
            slideLootDelay.Value = settings.iKillLootDelay;
            textLootDelay.Text = settings.iKillLootDelay.ToString();
            slideVaultDelay.Value = settings.iDHVaultMovementDelay;
            textVaultDelay.Text = settings.iDHVaultMovementDelay.ToString();
            slideKite0.Value = settings.iKiteDistanceBarb;
            textKite0.Text = settings.iKiteDistanceBarb.ToString();
            slideKite2.Value = settings.iKiteDistanceWiz;
            textKite2.Text = settings.iKiteDistanceWiz.ToString();
            slideKite3.Value = settings.iKiteDistanceWitch;
            textKite3.Text = settings.iKiteDistanceWitch.ToString();
            slideKite4.Value = settings.iKiteDistanceDemon;
            textKite4.Text = settings.iKiteDistanceDemon.ToString();
            if (settings.iTreasureGoblinPriority == 0)
            {
                checkTreasureNormal.IsChecked = false;
                checkTreasurePrioritize.IsChecked = false;
                checkTreasureKamikaze.IsChecked = false;
                checkTreasureIgnore.IsChecked = true;
            }
            else if (settings.iTreasureGoblinPriority == 1)
            {
                checkTreasureIgnore.IsChecked = false;
                checkTreasurePrioritize.IsChecked = false;
                checkTreasureKamikaze.IsChecked = false;
                checkTreasureNormal.IsChecked = true;
            }
            else if (settings.iTreasureGoblinPriority == 2)
            {
                checkTreasureIgnore.IsChecked = false;
                checkTreasureNormal.IsChecked = false;
                checkTreasureKamikaze.IsChecked = false;
                checkTreasurePrioritize.IsChecked = true;
            }
            else
            {
                checkTreasureIgnore.IsChecked = false;
                checkTreasureNormal.IsChecked = false;
                checkTreasurePrioritize.IsChecked = false;
                checkTreasureKamikaze.IsChecked = true;
            }

            if (settings.bIgnoreAllShrines)
            {
                checkIgnoreNone.IsChecked = false;
                checkIgnoreAll.IsChecked = true;
            }
            else
            {
                checkIgnoreAll.IsChecked = false;
                checkIgnoreNone.IsChecked = true;
            }
            checkBacktracking.IsChecked = settings.bEnableBacktracking;
            checkCritical.IsChecked = settings.bEnableCriticalMass;
            checkGrave.IsChecked = settings.bEnableCriticalMass;
            checkAvoidance.IsChecked = settings.bEnableAvoidance;
            checkGlobes.IsChecked = settings.bEnableGlobes;
            slideContainerRange.Value = settings.iContainerOpenRange;
            textContainerRange.Text = settings.iContainerOpenRange.ToString();
            slideDestructibleRange.Value = settings.iDestructibleAttackRange;
            textDestructibleRange.Text = settings.iDestructibleAttackRange.ToString();
            checkIgnoreCorpses.IsChecked = settings.bIgnoreCorpses;
            checkMovementAbilities.IsChecked = settings.bOutOfCombatMovementPowers;
            textTPS.Text = settings.iTPSAmount.ToString();
            slideTPS.Value = settings.iTPSAmount;
            checkTPS.IsChecked = settings.bEnableTPS;
            checkLogStucks.IsChecked = settings.bLogStucks;
            checkProwl.IsChecked = settings.bEnableProwl;
            textProwlKey.Text = sProwlAPIKey;
            checkEmail.IsChecked = settings.bEnableEmail;
            checkLegendaryNotify.IsChecked = settings.bEnableLegendaryNotifyScore;
            txtEmailAddress.Text = sEmailAddress;
            txtEmailPassword.Text = sEmailPassword;
            txtBotName.Text = sBotName;
            checkAndroid.IsChecked = settings.bEnableAndroid;
            textAndroidKey.Text = sAndroidAPIKey;
            checkUnstucker.IsChecked = settings.bEnableUnstucker;
            checkProfileReload.IsChecked = settings.bEnableProfileReloading;
            checkExtendedRange.IsChecked = settings.bExtendedKillRange;
            checkSelectiveWW.IsChecked = settings.bSelectiveWhirlwind;
            checkWrath90.IsChecked = settings.bWrath90Seconds;
            checkWaitWrath.IsChecked = settings.bWaitForWrath;
            checkKiteArchonOnly.IsChecked = settings.bKiteOnlyArchon;
            checkWaitArchonAzmo.IsChecked = settings.bWaitForArchon;
            checkGoblinWrath.IsChecked = settings.bGoblinWrath;
            checkFuryDumpWrath.IsChecked = settings.bFuryDumpWrath;
            checkFuryDumpAlways.IsChecked = settings.bFuryDumpAlways;
            checkDebugInfo.IsChecked = settings.bDebugInfo;
            checkMonkInna.IsChecked = settings.bMonkInnaSet;
            slidePot0.Value = Math.Floor(settings.dEmergencyHealthPotionBarb * 100);
            slidePot1.Value = Math.Floor(settings.dEmergencyHealthPotionMonk * 100);
            slidePot2.Value = Math.Floor(settings.dEmergencyHealthPotionWiz * 100);
            slidePot3.Value = Math.Floor(settings.dEmergencyHealthPotionWitch * 100);
            slidePot4.Value = Math.Floor(settings.dEmergencyHealthPotionDemon * 100);
            textPot0.Text = Math.Floor(settings.dEmergencyHealthPotionBarb * 100).ToString();
            textPot1.Text = Math.Floor(settings.dEmergencyHealthPotionMonk * 100).ToString();
            textPot2.Text = Math.Floor(settings.dEmergencyHealthPotionWiz * 100).ToString();
            textPot3.Text = Math.Floor(settings.dEmergencyHealthPotionWitch * 100).ToString();
            textPot4.Text = Math.Floor(settings.dEmergencyHealthPotionDemon * 100).ToString();
            slideGlobe0.Value = Math.Floor(settings.dEmergencyHealthGlobeBarb * 100);
            slideGlobe1.Value = Math.Floor(settings.dEmergencyHealthGlobeMonk * 100);
            slideGlobe2.Value = Math.Floor(settings.dEmergencyHealthGlobeWiz * 100);
            slideGlobe3.Value = Math.Floor(settings.dEmergencyHealthGlobeWitch * 100);
            slideGlobe4.Value = Math.Floor(settings.dEmergencyHealthGlobeDemon * 100);
            textGlobe0.Text = Math.Floor(settings.dEmergencyHealthGlobeBarb * 100).ToString();
            textGlobe1.Text = Math.Floor(settings.dEmergencyHealthGlobeMonk * 100).ToString();
            textGlobe2.Text = Math.Floor(settings.dEmergencyHealthGlobeWiz * 100).ToString();
            textGlobe3.Text = Math.Floor(settings.dEmergencyHealthGlobeWitch * 100).ToString();
            textGlobe4.Text = Math.Floor(settings.dEmergencyHealthGlobeDemon * 100).ToString();
            bSuppressEventChanges = false;
        }
        #endregion
        // ***************************************************
        // *********** END OF CONFIG WINDOW REGION ***********
        // ***************************************************



        // **********************************************************************************************
        // *****     Primary/"Default" functions, like logs, initialize, DB event handling etc.     *****
        // **********************************************************************************************

        private static void Log(string message, bool bIsDiagnostic = false)
        {
            string totalMessage = String.Format("[GilesTrinity] {0}", message);
            if (!bIsDiagnostic)
                Logging.Write(totalMessage);
            else
                Logging.WriteDiagnostic(totalMessage);
        }

        private static bool bPluginEnabled = false;

        private static Button btnPauseBot, btnTownRun;

        public void OnInitialize()
        {
            string battleTagName = "";
            try
            {
                battleTagName = ZetaDia.Service.CurrentHero.BattleTagName;
            }
            catch { }
            string sDemonBuddyPath = Assembly.GetEntryAssembly().Location;
            sTrinityPluginPath = Path.GetDirectoryName(sDemonBuddyPath) + @"\Plugins\GilesTrinity\";
            sTrinityConfigFile = Path.GetDirectoryName(sDemonBuddyPath) + @"\Settings\GilesTrinity.cfg";
            sTrinityEmailConfigFile = Path.GetDirectoryName(sDemonBuddyPath) + @"\Settings\" + battleTagName + @"-Email.cfg";
            Logging.WriteDiagnostic("Trinity Initialization, settings location=" + sTrinityConfigFile);
            BotMain.OnStart += GilesTrinityStart;
            // Force logging to disabled
            if (bDisableFileLogging)
                Zeta.Common.Logging.LogFileLevel = Zeta.Common.LogLevel.None;
            // Set up the pause button
            Application.Current.Dispatcher.Invoke(
            new System.Action(
            () =>
            {
                Window mainWindow = Application.Current.MainWindow;
                try
                {
                    mainWindow.Title = "DB - " + battleTagName;
                }
                catch
                {

                }
                var tab = mainWindow.FindName("tabControlMain") as TabControl;
                if (tab == null) return;
                var infoDumpTab = tab.Items[0] as TabItem;
                if (infoDumpTab == null) return;
                btnPauseBot = new Button
                {
                    Width = 100,
                    HorizontalAlignment = HorizontalAlignment.Left,
                    VerticalAlignment = VerticalAlignment.Top,
                    Margin = new Thickness(232, 6, 0, 0),
                    Content = "Pause Bot"
                };
                btnPauseBot.Click += buttonPause_Click;
                btnTownRun = new Button
                {
                    Width = 100,
                    HorizontalAlignment = HorizontalAlignment.Left,
                    VerticalAlignment = VerticalAlignment.Top,
                    Margin = new Thickness(232, 32, 0, 0),
                    Content = "Force Town Run"
                };
                btnTownRun.Click += buttonTownRun_Click;
                var grid = infoDumpTab.Content as Grid;
                if (grid == null) return;
                grid.Children.Add(btnPauseBot);
                grid.Children.Add(btnTownRun);
            }));
        }

        private static bool botispaused()
        {
            return bMainBotPaused;
        }

        // Force town-run button
        private static void buttonTownRun_Click(object sender, RoutedEventArgs e)
        {
            if (!BotMain.IsRunning || !ZetaDia.IsInGame || ZetaDia.IsLoadingWorld)
            {
                Logging.Write("[GilesTrinity] You can only force a town run while DemonBuddy is started and running!");
                return;
            }
            bGilesForcedVendoring = true;
            Logging.Write("[GilesTrinity] Town-run request received, will town-run at next possible moment.");
        }

        // Pause Button
        private static void buttonPause_Click(object sender, RoutedEventArgs e)
        {
            if (bMainBotPaused)
            {
                btnPauseBot.Content = "Pause Bot";
                bMainBotPaused = false;
                bMappedPlayerAbilities = false;
                lastChangedZigZag = DateTime.Today;
                bAlreadyMoving = false;
                lastMovementCommand = DateTime.Today;
            }
            else
            {
                BotMain.PauseWhile(botispaused);
                btnPauseBot.Content = "Unpause Bot";
                bMainBotPaused = true;
            }
        }

        public void OnPulse()
        {
        }

        public void OnEnabled()
        {
            if (!Directory.Exists(sTrinityPluginPath))
            {
                Log("Fatal Error - cannot enable plugin. Invalid path: " + sTrinityPluginPath);
                Log("Please check you have installed the plugin to the correct location, and then restart DemonBuddy and re-enable the plugin.");
                Log(@"Plugin should be installed to \<DemonBuddyFolder>\Plugins\GilesTrinity\");
            }
            else
            {
                bMappedPlayerAbilities = false;
                bPluginEnabled = true;
                LoadConfiguration();
                Navigator.PlayerMover = new GilesPlayerMover();
                Navigator.StuckHandler = new GilesStuckHandler();
                GameEvents.OnPlayerDied += GilesTrinityOnDeath;
                BotMain.OnStop += GilesTrinityHandleBotStop;
                GameEvents.OnGameJoined += GilesTrinityOnJoinGame;
                GameEvents.OnGameLeft += GilesTrinityOnLeaveGame;
                ITargetingProvider newCombatTargetingProvider = new GilesCombatTargetingReplacer();
                CombatTargeting.Instance.Provider = newCombatTargetingProvider;
                ITargetingProvider newLootTargetingProvider = new GilesLootTargetingProvider();
                LootTargeting.Instance.Provider = newLootTargetingProvider;
                ITargetingProvider newObstacleTargetingProvider = new GilesObstacleTargetingProvider();
                ObstacleTargeting.Instance.Provider = newObstacleTargetingProvider;
                // Safety check incase DB "OnStart" event didn't fire properly
                if (BotMain.IsRunning)
                {
                    GilesTrinityStart(null);
                    if (ZetaDia.IsInGame)
                        GilesTrinityOnJoinGame(null, null);
                }
                // Carguy's ticks-per-second feature
                if (settings.bEnableTPS)
                {
                    BotMain.TicksPerSecond = (int)settings.iTPSAmount;
                }
                Log("************************************");
                Log("ENABLED: " + Description + " now in action!");
                Log("************************************");


                //interpreter.init();
            }
        }

        public void OnDisabled()
        {
            bPluginEnabled = false;
            Navigator.PlayerMover = new DefaultPlayerMover();
            Navigator.StuckHandler = new DefaultStuckHandler();
            CombatTargeting.Instance.Provider = new DefaultCombatTargetingProvider();
            LootTargeting.Instance.Provider = new DefaultLootTargetingProvider();
            ObstacleTargeting.Instance.Provider = new DefaultObstacleTargetingProvider();
            GameEvents.OnPlayerDied -= GilesTrinityOnDeath;
            BotMain.OnStop -= GilesTrinityHandleBotStop;
            GameEvents.OnGameJoined -= GilesTrinityOnJoinGame;
            GameEvents.OnGameLeft -= GilesTrinityOnLeaveGame;
            Log("DISABLED: Giles Trinity is now shut down...");
        }

        public void OnShutdown()
        {
        }

        // On death, clear the timers for all abilities
        private static DateTime lastDied = DateTime.Today;
        private static int iTotalDeaths = 0;
        private void GilesTrinityOnDeath(object src, EventArgs mea)
        {
            if (DateTime.Now.Subtract(lastDied).TotalSeconds > 10)
            {
                lastDied = DateTime.Now;
                iTotalDeaths++;
                iDeathsThisRun++;
                dictAbilityLastUse = new Dictionary<SNOPower, DateTime>(dictAbilityLastUseDefaults);
                vBacktrackList = new SortedList<int, Vector3>();
                iTotalBacktracks = 0;
                GilesPlayerMover.iTotalAntiStuckAttempts = 1;
                GilesPlayerMover.vSafeMovementLocation = Vector3.Zero;

                // Does Trinity need to handle deaths?
                if (iMaxDeathsAllowed > 0)
                {
                    if (iDeathsThisRun >= iMaxDeathsAllowed)
                    {
                        Logging.Write("[GilesTrinity] You have died too many times. Now restarting the game.");
                        string sUseProfile = GilesTrinity.sFirstProfileSeen;
                        ProfileManager.Load(!string.IsNullOrEmpty(sUseProfile)
                                                ? sUseProfile
                                                : Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile);
                        Thread.Sleep(1000);
                        GilesResetEverythingNewGame();
                        ZetaDia.Service.Games.LeaveGame();
                        Thread.Sleep(10000);
                    }
                    else
                    {
                        Logging.Write("[GilesTrinity] I'm sorry, but I seem to have let you die :( Now restarting the current profile.");
                        ProfileManager.Load(Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile);
                        Thread.Sleep(2000);
                    }
                }

            }
        }

        // When the bot stops, output a final item-stats report so it is as up-to-date as can be
        private void GilesTrinityHandleBotStop(IBot bot)
        {
            // Issue final reports
            OutputReport();
            vBacktrackList = new SortedList<int, Vector3>();
            iTotalBacktracks = 0;
            GilesPlayerMover.iTotalAntiStuckAttempts = 1;
            GilesPlayerMover.vSafeMovementLocation = Vector3.Zero;
            GilesPlayerMover.vOldPosition = Vector3.Zero;
            GilesPlayerMover.iTimesReachedStuckPoint = 0;
            GilesPlayerMover.timeLastRecordedPosition = DateTime.Today;
            GilesPlayerMover.timeStartedUnstuckMeasure = DateTime.Today;
            hashUseOnceID = new HashSet<int>();
            dictUseOnceID = new Dictionary<int, int>();
            dictRandomID = new Dictionary<int, int>();
            iMaxDeathsAllowed = 0;
            iDeathsThisRun = 0;
        }


        // How many total leave games, for stat-tracking?
        public static int iTotalJoinGames = 0;
        // Each time we join & leave a game, might as well clear the hashset of looked-at dropped items - just to keep it smaller
        private static void GilesTrinityOnJoinGame(object src, EventArgs mea)
        {
            iTotalJoinGames++;
            GilesResetEverythingNewGame();
        }

        // How many total leave games, for stat-tracking?
        public static int iTotalLeaveGames = 0;
        // Each time we join & leave a game, might as well clear the hashset of looked-at dropped items - just to keep it smaller
        private static void GilesTrinityOnLeaveGame(object src, EventArgs mea)
        {
            iTotalLeaveGames++;
            GilesResetEverythingNewGame();
        }

        public static int iTotalProfileRecycles = 0;
        public static void GilesResetEverythingNewGame()
        {
            hashUseOnceID = new HashSet<int>();
            dictUseOnceID = new Dictionary<int, int>();
            iMaxDeathsAllowed = 0;
            iDeathsThisRun = 0;
            _hashsetItemStatsLookedAt = new HashSet<int>();
            _hashsetItemPicksLookedAt = new HashSet<int>();
            _hashsetItemFollowersIgnored = new HashSet<int>();
            _dictItemStashAttempted = new Dictionary<int, int>();
            hashRGUIDIgnoreBlacklist = new HashSet<int>();
            hashRGUIDTemporaryIgnoreBlacklist = new HashSet<int>();
            vBacktrackList = new SortedList<int, Vector3>();
            iTotalBacktracks = 0;
            bMappedPlayerAbilities = false;
            GilesPlayerMover.iTotalAntiStuckAttempts = 1;
            GilesPlayerMover.vSafeMovementLocation = Vector3.Zero;
            GilesPlayerMover.vOldPosition = Vector3.Zero;
            GilesPlayerMover.iTimesReachedStuckPoint = 0;
            GilesPlayerMover.timeLastRecordedPosition = DateTime.Today;
            GilesPlayerMover.timeStartedUnstuckMeasure = DateTime.Today;
            GilesPlayerMover.iTimesReachedMaxUnstucks = 0;
            GilesPlayerMover.iCancelUnstuckerForSeconds = 0;
            GilesPlayerMover.timeCancelledUnstuckerFor = DateTime.Today;
            // Reset all the caches
            dictGilesObjectTypeCache = new Dictionary<int, GilesObjectType>();
            dictGilesMonsterAffixCache = new Dictionary<int, MonsterAffixes>();
            dictGilesMaxHealthCache = new Dictionary<int, double>();
            dictGilesLastHealthCache = new Dictionary<int, double>();
            dictGilesLastHealthChecked = new Dictionary<int, int>();
            dictGilesBurrowedCache = new Dictionary<int, bool>();
            dictGilesActorSNOCache = new Dictionary<int, int>();
            dictGilesACDGUIDCache = new Dictionary<int, int>();
            dictGilesInternalNameCache = new Dictionary<int, string>();
            dictGilesGameBalanceIDCache = new Dictionary<int, int>();
            dictGilesDynamicIDCache = new Dictionary<int, int>();
            dictGilesVectorCache = new Dictionary<int, Vector3>();
            dictGilesGoldAmountCache = new Dictionary<int, int>();
            dictGilesQualityCache = new Dictionary<int, ItemQuality>();
            dictGilesQualityRechecked = new Dictionary<int, bool>();
            dictGilesPickupItem = new Dictionary<int, bool>();
            dictSummonedByID = new Dictionary<int, int>();
            dictTotalInteractionAttempts = new Dictionary<int, int>();

            listProfilesLoaded = new List<string>();
            sLastProfileSeen = "";
            sFirstProfileSeen = "";
        }


        // **********************************************************************************************
        // *****                          Prowl Notification Support                                *****
        // **********************************************************************************************

        public static Queue<ProwlNotification> pushQueue = new Queue<ProwlNotification>();

        public struct ProwlNotification
        {
            public string Event { get; set; }
            public string Description { get; set; }
            public ProwlNotificationPriority Priority { get; set; }
        }

        public enum ProwlNotificationPriority : sbyte
        {
            VeryLow = -2,
            Moderate = -1,
            Normal = 0,
            High = 1,
            Emergency = 2
        }

        public static void AddNotificationToQueue(string description, string eventName, ProwlNotificationPriority priority)
        {
            // Queue the notification message
            var newNotification =
                    new ProwlNotification
                    {
                        Description = description,
                        Event = eventName,
                        Priority = priority
                    };
            pushQueue.Enqueue(newNotification);
        }

        public static void SendNotification(ProwlNotification notification)
        {
            if (settings.bEnableProwl && sProwlAPIKey != "")
            {
                var newNotification =
                        new ProwlNotification
                        {
                            Description = notification.Description,
                            Event = notification.Event,
                            Priority = notification.Priority
                        };
                try
                {
                    PostNotification(newNotification);
                }
                catch
                {

                }
            }
            if (settings.bEnableAndroid && sAndroidAPIKey != "")
            {
                var newNotification =
                        new ProwlNotification
                        {
                            Description = notification.Description,
                            Event = notification.Event,
                            Priority = notification.Priority
                        };
                try
                {
                    PostNotification(newNotification, true);
                }
                catch
                {

                }
            }
        }

        public static void PostNotification(ProwlNotification notification_, bool bForAndroid = false)
        {
            string prowlUrlSb = !bForAndroid ? @"https://prowl.weks.net/publicapi/add" : @"https://www.notifymyandroid.com/publicapi/notify";
            string sThisAPIKey = !bForAndroid ? sProwlAPIKey : sAndroidAPIKey;
            prowlUrlSb += "?apikey=" + HttpUtility.UrlEncode(sThisAPIKey) +
                          "&application=" + HttpUtility.UrlEncode("GilesTrinity") +
                          "&description=" + HttpUtility.UrlEncode(notification_.Description) +
                          "&event=" + HttpUtility.UrlEncode(notification_.Event) +
                          "&priority=" + HttpUtility.UrlEncode(notification_.Priority.ToString());
            var updateRequest =
                (HttpWebRequest)WebRequest.Create(prowlUrlSb.ToString());
            updateRequest.ContentLength = 0;
            updateRequest.ContentType = "application/x-www-form-urlencoded";
            updateRequest.Method = "POST";
            //updateRequest.Timeout = 5000;

            var postResponse = default(WebResponse);
            try
            {
                postResponse = updateRequest.GetResponse();
            }
            finally
            {
                if (postResponse != null)
                    postResponse.Close();
            }
        }

    } // End of main routines


    // **********************************************************************************************
    // **********************************************************************************************
    // **********************************************************************************************
    // **********************************************************************************************
    // **********************************************************************************************
    // *****                             Giles Custom XML Codes                                 *****
    // **********************************************************************************************
    // **********************************************************************************************
    // **********************************************************************************************
    // **********************************************************************************************
    // **********************************************************************************************

    // Note to self: No more sandwich :(

    // **********************************************************************************************
    // *****                       TrinityTownRun forces a town-run request                     *****
    // **********************************************************************************************

    [XmlElement("TrinityTownRun")]
    public class TrinityTownRunTag : ProfileBehavior
    {
        private bool m_IsDone = false;
        public override bool IsDone
        {
            get { return m_IsDone; }
        }

        protected override Composite CreateBehavior()
        {
            return new Zeta.TreeSharp.Action(ret =>
            {
                Logging.Write("[GilesTrinity] Town-run request received, will town-run at next possible moment.");
                GilesTrinity.bGilesForcedVendoring = true;
                m_IsDone = true;
            });
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }
    }

    // **********************************************************************************************
    // *****             TrinityLoadProfile will load the specified XML profile up              *****
    // **********************************************************************************************

    [XmlElement("TrinityLoadProfile")]
    public class TrinityLoadProfile : ProfileBehavior
    {
        private bool m_IsDone = false;
        private string sFileName;
        private string sExitString;
        private string sNoDelay;
        public override bool IsDone
        {
            get { return m_IsDone; }
        }

        protected override Composite CreateBehavior()
        {
            return new Zeta.TreeSharp.Action(ret =>
            {
                bool bExitGame = Exit != null && Exit.ToLower() == "true";
                string sThisProfileString = File;
                // See if there are multiple profile choices, if so split them up and pick a random one
                if (sThisProfileString.Contains("!"))
                {
                    string[] sProfileChoices;
                    sProfileChoices = sThisProfileString.Split(new string[] { "!" }, StringSplitOptions.None);
                    Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber));
                    int iChooseProfile = rndNum.Next(sProfileChoices.Count());
                    sThisProfileString = sProfileChoices[iChooseProfile];
                }
                // Now calculate our current path by checking the currently loaded profile
                string sCurrentProfilePath = Path.GetDirectoryName(Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile);
                // And prepare a full string of the path, and the new .xml file name
                string sNextProfile = sCurrentProfilePath + @"\" + sThisProfileString;
                Logging.Write("[GilesTrinity] Loading new profile.");
                ProfileManager.Load(sNextProfile);
                // A quick nap-time helps prevent some funny issues
                if (NoDelay == null || NoDelay.ToLower() != "true")
                    Thread.Sleep(3000);
                else
                    Thread.Sleep(300);
                // See if the XML tag requested we exit the game after loading this profile or not
                if (bExitGame)
                {
                    Logging.Write("[GilesTrinity] Exiting game to continue with next profile.");
                    // Attempt to teleport to town first for a quicker exit
                    int iSafetyLoops = 0;
                    while (!ZetaDia.Me.IsInTown)
                    {
                        iSafetyLoops++;
                        GilesTrinity.WaitWhileAnimating(5, true);
                        ZetaDia.Me.UsePower(SNOPower.UseStoneOfRecall, ZetaDia.Me.Position, ZetaDia.Me.WorldDynamicId, -1);
                        Thread.Sleep(1000);
                        GilesTrinity.WaitWhileAnimating(1000, true);
                        if (iSafetyLoops > 5)
                            break;
                    }
                    Thread.Sleep(1000);
                    ZetaDia.Service.Games.LeaveGame();
                    GilesTrinity.GilesResetEverythingNewGame();
                    // Wait for 10 second log out timer if not in town, else wait for 3 seconds instead
                    Thread.Sleep(!ZetaDia.Me.IsInTown ? 10000 : 3000);
                } // Check if we want to restart the game
                m_IsDone = true;
            });
        }

        [XmlAttribute("exit")]
        public string Exit
        {
            get
            {
                return sExitString;
            }
            set
            {
                sExitString = value;
            }
        }

        [XmlAttribute("nodelay")]
        public string NoDelay
        {
            get
            {
                return sNoDelay;
            }
            set
            {
                sNoDelay = value;
            }
        }

        [XmlAttribute("file")]
        public string File
        {
            get
            {
                return sFileName;
            }
            set
            {
                sFileName = value;
            }
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }
    }

    // **********************************************************************************************
    // *****    TrinityMaxDeaths tells Trinity to handle deaths and exit game after X deaths    *****
    // **********************************************************************************************

    [XmlElement("TrinityMaxDeaths")]
    public class TrinityMaxDeathsTag : ProfileBehavior
    {
        private bool m_IsDone = false;
        private int iMaxDeaths;
        private string sReset;
        public override bool IsDone
        {
            get { return m_IsDone; }
        }

        protected override Composite CreateBehavior()
        {
            return new Zeta.TreeSharp.Action(ret =>
            {
                if (MaxDeaths != GilesTrinity.iMaxDeathsAllowed)
                    Logging.Write("[GilesTrinity] Max deaths set by profile. Trinity now handling deaths, and will restart the game after " + MaxDeaths.ToString());

                GilesTrinity.iMaxDeathsAllowed = MaxDeaths;
                if (Reset != null && Reset.ToLower() == "true")
                    GilesTrinity.iDeathsThisRun = 0;
                m_IsDone = true;
            });
        }


        [XmlAttribute("reset")]
        public string Reset
        {
            get
            {
                return sReset;
            }
            set
            {
                sReset = value;
            }
        }

        [XmlAttribute("max")]
        public int MaxDeaths
        {
            get
            {
                return iMaxDeaths;
            }
            set
            {
                iMaxDeaths = value;
            }
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }
    }

    // **********************************************************************************************
    // *****      TrinityInteract attempts a blind object-use of an SNO without movement        *****
    // **********************************************************************************************

    [XmlElement("TrinityInteract")]
    public class TrinityInteractTag : ProfileBehavior
    {
        private bool m_IsDone = false;
        private int iSNOID;
        public override bool IsDone
        {
            get { return m_IsDone; }
        }

        protected override Composite CreateBehavior()
        {
            return new Zeta.TreeSharp.Action(ret =>
            {
                float fClosestRange = -1;
                int iACDGuid = -1;
                Vector3 vMyLocation = ZetaDia.Me.Position;
                foreach (DiaObject thisobject in ZetaDia.Actors.GetActorsOfType<DiaObject>(true, false).Where<DiaObject>(a => a.ActorSNO == SNOID))
                {
                    if (fClosestRange == -1 || thisobject.Position.Distance(vMyLocation) <= fClosestRange)
                    {
                        fClosestRange = thisobject.Position.Distance(vMyLocation);
                        iACDGuid = thisobject.ACDGuid;
                    }
                }

                if (iACDGuid != -1)
                {
                    try
                    {
                        ZetaDia.Me.UsePower(SNOPower.Axe_Operate_Gizmo, Vector3.Zero, 0, iACDGuid);
                    }
                    catch
                    {
                        Logging.WriteDiagnostic("[GilesTrinity] There was a memory/DB failure trying to follow the TrinityInteract XML tag on SNO " + SNOID.ToString());
                    }
                }
                m_IsDone = true;
            });
        }


        [XmlAttribute("snoid")]
        public int SNOID
        {
            get
            {
                return iSNOID;
            }
            set
            {
                iSNOID = value;
            }
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }
    }

    // **********************************************************************************************
    // *****                  Trinity Log lets profiles send log messages to DB                 *****
    // **********************************************************************************************

    [XmlElement("TrinityLog")]
    public class TrinityLogTag : ProfileBehavior
    {
        private bool m_IsDone = false;
        private string sLogOutput;
        private string sLogLevel;
        public override bool IsDone
        {
            get { return m_IsDone; }
        }

        protected override Composite CreateBehavior()
        {
            return new Zeta.TreeSharp.Action(ret =>
            {
                if (Level != null && Level.ToLower() == "diagnostic")
                    Logging.WriteDiagnostic(Output);
                else
                    Logging.Write(Output);
                m_IsDone = true;
            });
        }

        [XmlAttribute("level", true)]
        public string Level
        {
            get
            {
                return sLogLevel;
            }
            set
            {
                sLogLevel = value;
            }
        }

        [XmlAttribute("output", true)]
        public string Output
        {
            get
            {
                return sLogOutput;
            }
            set
            {
                sLogOutput = value;
            }
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }
    }


    // ****************************************************************************************************
    // ***** TrinityMoveTo moves in a straight line without any navigation hits, and allows tag-skips *****
    // ****************************************************************************************************

    [XmlElement("TrinityMoveTo")]
    public class TrinityMoveToTag : ProfileBehavior
    {
        private bool m_IsDone;
        private float fPosX;
        private float fPosY;
        private float fPosZ;
        private float fPathPrecision;
        private float fRandomizedDistance;
        private string sDestinationName;
        private string sNoSkip;
        private Vector3? vMainVector;

        protected override Composite CreateBehavior()
        {
            Composite[] children = new Composite[2];
            Composite[] compositeArray = new Composite[2];
            compositeArray[0] = new Zeta.TreeSharp.Action(new ActionSucceedDelegate(FlagTagAsCompleted));
            children[0] = new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckDistanceWithinPathPrecision), new Sequence(compositeArray));
            ActionDelegate actionDelegateMove = new ActionDelegate(GilesMoveToLocation);
            Sequence sequenceblank = new Sequence(
                new Zeta.TreeSharp.Action(actionDelegateMove)
                );
            children[1] = sequenceblank;
            return new PrioritySelector(children);
        }

        private RunStatus GilesMoveToLocation(object ret)
        {
            // First check if we can skip ahead because we recently moved here
            if (!GilesTrinity.settings.bEnableBacktracking && (NoSkip == null || NoSkip.ToLower() != "true"))
            {
                if (GilesTrinity.hashSkipAheadAreaCache.Any())
                {
                    // Loop through all the skip ahead zones and see if one of them is within radius of our intended destination to skip ahead
                    foreach (GilesTrinity.GilesObstacle thisObject in GilesTrinity.hashSkipAheadAreaCache)
                    {
                        if (thisObject.vThisLocation.Distance(Position) <= thisObject.fThisRadius)
                        {
                            Logging.WriteDiagnostic("[GilesTrinity] Skipping ahead from moveto " + Position.ToString() + " to next moveto.");
                            GilesTrinity.bSkipAheadAGo = true;
                            return RunStatus.Success;
                        }
                    }
                    GilesTrinity.hashSkipAheadAreaCache = new HashSet<GilesTrinity.GilesObstacle>();
                }
            }
            else
            {
                GilesTrinity.hashSkipAheadAreaCache = new HashSet<GilesTrinity.GilesObstacle>();
            }
            // Now use Trinity movement to try a direct movement towards that location
            Navigator.PlayerMover.MoveTowards(Position);
            return RunStatus.Success;
        }

        private bool CheckDistanceWithinPathPrecision(object object_0)
        {
            // First see if we should skip ahead one move because we were already at that location
            if (GilesTrinity.bSkipAheadAGo)
            {
                GilesTrinity.bSkipAheadAGo = false;
                return true;
            }
            // Ok not skipping, now see if we are already within pathprecision range of that location
            return (ZetaDia.Me.Position.Distance(Position) <= Math.Max(PathPrecision, Navigator.PathPrecision));
        }

        private void FlagTagAsCompleted(object object_0)
        {
            m_IsDone = true;
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }


        public override bool IsDone
        {
            get
            {
                if (IsActiveQuestStep)
                {
                    return m_IsDone;
                }
                return true;
            }
        }

        [XmlAttribute("noskip")]
        public string NoSkip
        {
            get
            {
                return sNoSkip;
            }
            set
            {
                sNoSkip = value;
            }
        }

        [XmlAttribute("name")]
        public string Name
        {
            get
            {
                return sDestinationName;
            }
            set
            {
                sDestinationName = value;
            }
        }

        [XmlAttribute("pathPrecision")]
        public float PathPrecision
        {
            get
            {
                return fPathPrecision;
            }
            set
            {
                fPathPrecision = value;
            }
        }

        public Vector3 Position
        {
            get
            {
                if (!vMainVector.HasValue)
                {
                    if (UnsafeRandomDistance == 0f)
                    {
                        vMainVector = new Vector3(X, Y, Z);
                    }
                    else
                    {
                        float degrees = new Random().Next(0, 360);
                        vMainVector = new Vector3?(MathEx.GetPointAt(new Vector3(X, Y, Z), (float)(new Random().NextDouble() * UnsafeRandomDistance), MathEx.ToRadians(degrees)));
                    }
                }
                return vMainVector.Value;
            }
        }

        [XmlAttribute("unsafeRandomDistance")]
        public float UnsafeRandomDistance
        {
            get
            {
                return fRandomizedDistance;
            }
            set
            {
                fRandomizedDistance = value;
            }
        }

        [XmlAttribute("x")]
        public float X
        {
            get
            {
                return fPosX;
            }
            set
            {
                fPosX = value;
            }
        }

        [XmlAttribute("y")]
        public float Y
        {
            get
            {
                return fPosY;
            }
            set
            {
                fPosY = value;
            }
        }

        [XmlAttribute("z")]
        public float Z
        {
            get
            {
                return fPosZ;
            }
            set
            {
                fPosZ = value;
            }
        }
    }

    // ****************************************************************************************************
    // ***** TrinityMoveTo moves in a straight line without any navigation hits, and allows tag-skips *****
    // ****************************************************************************************************

    [XmlElement("TrinityMoveToSNO")]
    public class TrinityMoveToSNOTag : ProfileBehavior
    {
        private bool m_IsDone;
        private float fPathPrecision;
        private int iSNOID;
        private string sDestinationName;

        protected override Composite CreateBehavior()
        {
            Composite[] children = new Composite[2];
            Composite[] compositeArray = new Composite[2];
            compositeArray[0] = new Zeta.TreeSharp.Action(new ActionSucceedDelegate(FlagTagAsCompleted));
            children[0] = new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckDistanceWithinPathPrecision), new Sequence(compositeArray));
            ActionDelegate actionDelegateMove = new ActionDelegate(GilesMoveToLocation);
            Sequence sequenceblank = new Sequence(
                new Zeta.TreeSharp.Action(actionDelegateMove)
                );
            children[1] = sequenceblank;
            return new PrioritySelector(children);
        }

        private RunStatus GilesMoveToLocation(object ret)
        {
            DiaObject tempObject = ZetaDia.Actors.GetActorsOfType<DiaObject>(true, false).FirstOrDefault<DiaObject>(a => a.ActorSNO == SNOID);
            if (tempObject != null)
            {
                Navigator.PlayerMover.MoveTowards(tempObject.Position);
                return RunStatus.Success;
            }
            return RunStatus.Success;
        }

        private bool CheckDistanceWithinPathPrecision(object object_0)
        {
            DiaObject tempObject = ZetaDia.Actors.GetActorsOfType<DiaObject>(true, false).FirstOrDefault<DiaObject>(a => a.ActorSNO == SNOID);
            if (tempObject != null)
            {
                return (ZetaDia.Me.Position.Distance(tempObject.Position) <= Math.Max(PathPrecision, Navigator.PathPrecision));
            }
            return false;
        }

        private void FlagTagAsCompleted(object object_0)
        {
            m_IsDone = true;
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }


        public override bool IsDone
        {
            get
            {
                if (IsActiveQuestStep)
                {
                    return m_IsDone;
                }
                return true;
            }
        }

        [XmlAttribute("name")]
        public string Name
        {
            get
            {
                return sDestinationName;
            }
            set
            {
                sDestinationName = value;
            }
        }

        [XmlAttribute("pathPrecision")]
        public float PathPrecision
        {
            get
            {
                return fPathPrecision;
            }
            set
            {
                fPathPrecision = value;
            }
        }

        [XmlAttribute("snoid")]
        public int SNOID
        {
            get
            {
                return iSNOID;
            }
            set
            {
                iSNOID = value;
            }
        }
    }
    // Note to self: Could manually start a new game with;
    //ZetaDia.Service.Games.CreateGame(createGameParams.Act, createGameParams.Difficulty, createGameParams.Quest, createGameParams.Step, createGameParams.ResumeFromSave, createGameParams.IsPrivate);

    // **********************************************************************************************
    // *****         Trinity If perfectly mimics DB If - this is just for experimenting         *****
    // **********************************************************************************************

    [XmlElement("TrinityIf")]
    public class TrinityIfTag : ComplexNodeTag, IPythonExecutable
    {
        private bool? bComplexDoneCheck;
        private bool? bAlreadyCompleted;
        private Func<bool> funcConditionalProcess;
        private static Func<ProfileBehavior, bool> funcBehaviorProcess;
        private string sConditionString;

        public void CompilePython()
        {

            ScriptManager.GetCondition(Condition);
        }

        protected override Composite CreateBehavior()
        {
            PrioritySelector decorated = new PrioritySelector(new Composite[0]);
            foreach (ProfileBehavior behavior in base.GetNodes())
            {
                decorated.AddChild(behavior.Behavior);
            }
            return new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckNotAlreadyDone), decorated);
        }

        public bool GetConditionExec()
        {
            bool flag;
            try
            {
                if (Conditional == null)
                {
                    Conditional = ScriptManager.GetCondition(Condition);
                }
                flag = Conditional();
            }
            catch (Exception exception)
            {
                Logging.WriteDiagnostic(ScriptManager.FormatSyntaxErrorException(exception));
                BotMain.Stop(false, "");
                throw;
            }
            return flag;
        }

        private bool CheckNotAlreadyDone(object object_0)
        {
            return !IsDone;
        }

        public override void ResetCachedDone()
        {
            foreach (ProfileBehavior behavior in Body)
            {
                behavior.ResetCachedDone();
            }
            bComplexDoneCheck = null;
        }

        private static bool CheckBehaviorIsDone(ProfileBehavior profileBehavior)
        {
            return profileBehavior.IsDone;
        }

        [XmlAttribute("condition", true)]
        public string Condition
        {
            get
            {
                return sConditionString;
            }
            set
            {
                sConditionString = value;
            }
        }

        public Func<bool> Conditional
        {
            get
            {
                return funcConditionalProcess;
            }
            set
            {
                funcConditionalProcess = value;
            }
        }

        public override bool IsDone
        {
            get
            {
                // Make sure we've not already completed this if
                if (bAlreadyCompleted.HasValue && bAlreadyCompleted == true)
                {
                    return true;
                }
                // First check the actual "if" conditions if we haven't already
                if (!bComplexDoneCheck.HasValue)
                {
                    bComplexDoneCheck = new bool?(GetConditionExec());
                }
                if (bComplexDoneCheck == false)
                {
                    return true;
                }
                // Ok we've already checked that and it was false FIRST check, so now go purely on behavior-done flag
                if (funcBehaviorProcess == null)
                {
                    funcBehaviorProcess = new Func<ProfileBehavior, bool>(CheckBehaviorIsDone);
                }
                bool bAllChildrenDone = Body.All<ProfileBehavior>(funcBehaviorProcess);
                if (bAllChildrenDone)
                {
                    bAlreadyCompleted = true;
                }
                return bAllChildrenDone;
            }
        }
    }

    // **********************************************************************************************
    // *****     TrinityIfWithinRange checks an SNO is in range and processes child nodes       *****
    // **********************************************************************************************

    [XmlElement("TrinityIfSNOInRange")]
    public class IfSNOInRangeTag : ComplexNodeTag
    {
        private bool? bComplexDoneCheck;
        private bool? bAlreadyCompleted;
        private Func<bool> funcConditionalProcess;
        private static Func<ProfileBehavior, bool> funcBehaviorProcess;
        private int iSNOID;
        private float fRadius;
        private string sType;

        protected override Composite CreateBehavior()
        {
            PrioritySelector decorated = new PrioritySelector(new Composite[0]);
            foreach (ProfileBehavior behavior in base.GetNodes())
            {
                decorated.AddChild(behavior.Behavior);
            }
            return new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckNotAlreadyDone), decorated);
        }

        public bool GetConditionExec()
        {
            bool flag;
            Vector3 vMyLocation = ZetaDia.Me.Position;
            if (sType != null && sType == "reverse")
                flag = ZetaDia.Actors.GetActorsOfType<DiaObject>(true, false).FirstOrDefault<DiaObject>(a => a.ActorSNO == SNOID && a.Position.Distance(vMyLocation) <= Range) == null;
            else
                flag = (ZetaDia.Actors.GetActorsOfType<DiaObject>(true, false).FirstOrDefault<DiaObject>(a => a.ActorSNO == SNOID && a.Position.Distance(vMyLocation) <= Range) != null);
            return flag;
        }

        private bool CheckNotAlreadyDone(object object_0)
        {
            return !IsDone;
        }

        public override void ResetCachedDone()
        {
            foreach (ProfileBehavior behavior in Body)
            {
                behavior.ResetCachedDone();
            }
            bComplexDoneCheck = null;
        }

        private static bool CheckBehaviorIsDone(ProfileBehavior profileBehavior)
        {
            return profileBehavior.IsDone;
        }

        [XmlAttribute("snoid")]
        public int SNOID
        {
            get
            {
                return iSNOID;
            }
            set
            {
                iSNOID = value;
            }
        }

        [XmlAttribute("range")]
        public float Range
        {
            get
            {
                return fRadius;
            }
            set
            {
                fRadius = value;
            }
        }

        [XmlAttribute("type")]
        public string Type
        {
            get
            {
                return sType;
            }
            set
            {
                sType = value;
            }
        }

        public Func<bool> Conditional
        {
            get
            {
                return funcConditionalProcess;
            }
            set
            {
                funcConditionalProcess = value;
            }
        }

        public override bool IsDone
        {
            get
            {
                // Make sure we've not already completed this tag
                if (bAlreadyCompleted.HasValue && bAlreadyCompleted == true)
                {
                    return true;
                }
                if (!bComplexDoneCheck.HasValue)
                {
                    bComplexDoneCheck = new bool?(GetConditionExec());
                }
                if (bComplexDoneCheck == false)
                {
                    return true;
                }
                if (funcBehaviorProcess == null)
                {
                    funcBehaviorProcess = new Func<ProfileBehavior, bool>(CheckBehaviorIsDone);
                }
                bool bAllChildrenDone = Body.All<ProfileBehavior>(funcBehaviorProcess);
                if (bAllChildrenDone)
                {
                    bAlreadyCompleted = true;
                }
                return bAllChildrenDone;
            }
        }
    }


    // **********************************************************************************************
    // *****     TrinityRandom assigns a random value between min and max to a specified id     *****
    // **********************************************************************************************

    [XmlElement("TrinityRandomRoll")]
    public class TrinityRandomRollTag : ProfileBehavior
    {
        private bool m_IsDone = false;
        private int iID;
        private int iMin;
        private int iMax;
        public override bool IsDone
        {
            get { return m_IsDone; }
        }

        protected override Composite CreateBehavior()
        {
            return new Zeta.TreeSharp.Action(ret =>
            {
                // Generate a random value between the selected min-max range, and assign it to our dictionary of random values
                int iOldValue;
                Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber));
                int iNewRandomValue = (rndNum.Next((Max - Min) + 1)) + Min;
                Logging.Write("[GilesTrinity] Generating RNG for profile between " + Min.ToString() + " and " + Max.ToString() + ", result=" + iNewRandomValue.ToString());
                if (!GilesTrinity.dictRandomID.TryGetValue(ID, out iOldValue))
                {
                    GilesTrinity.dictRandomID.Add(ID, iNewRandomValue);
                }
                else
                {
                    GilesTrinity.dictRandomID[ID] = iNewRandomValue;
                }
                m_IsDone = true;
            });
        }


        [XmlAttribute("id")]
        public int ID
        {
            get
            {
                return iID;
            }
            set
            {
                iID = value;
            }
        }
        [XmlAttribute("min")]
        public int Min
        {
            get
            {
                return iMin;
            }
            set
            {
                iMin = value;
            }
        }
        [XmlAttribute("max")]
        public int Max
        {
            get
            {
                return iMax;
            }
            set
            {
                iMax = value;
            }
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }
    }

    // **********************************************************************************************
    // *****  TrinityIfRandom only runs the container stuff if the given id is the given value  *****
    // **********************************************************************************************

    [XmlElement("TrinityIfRandom")]
    public class TrinityIfRandomTag : ComplexNodeTag
    {
        private bool? bComplexDoneCheck;
        private bool? bAlreadyCompleted;
        private Func<bool> funcConditionalProcess;
        private static Func<ProfileBehavior, bool> funcBehaviorProcess;
        private int iID;
        private int iResult;

        protected override Composite CreateBehavior()
        {
            PrioritySelector decorated = new PrioritySelector(new Composite[0]);
            foreach (ProfileBehavior behavior in base.GetNodes())
            {
                decorated.AddChild(behavior.Behavior);
            }
            return new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckNotAlreadyDone), decorated);
        }

        public bool GetConditionExec()
        {
            int iOldValue;
            // If the dictionary value doesn't even exist, FAIL!
            if (!GilesTrinity.dictRandomID.TryGetValue(ID, out iOldValue))
                return false;
            // Ok, do the results match up what we want? then SUCCESS!
            if (iOldValue == Result)
                return true;
            // No? Fail!
            return false;
        }

        private bool CheckNotAlreadyDone(object object_0)
        {
            return !IsDone;
        }

        public override void ResetCachedDone()
        {
            foreach (ProfileBehavior behavior in Body)
            {
                behavior.ResetCachedDone();
            }
            bComplexDoneCheck = null;
        }

        private static bool CheckBehaviorIsDone(ProfileBehavior profileBehavior)
        {
            return profileBehavior.IsDone;
        }

        [XmlAttribute("id")]
        public int ID
        {
            get
            {
                return iID;
            }
            set
            {
                iID = value;
            }
        }

        [XmlAttribute("result")]
        public int Result
        {
            get
            {
                return iResult;
            }
            set
            {
                iResult = value;
            }
        }

        public Func<bool> Conditional
        {
            get
            {
                return funcConditionalProcess;
            }
            set
            {
                funcConditionalProcess = value;
            }
        }

        public override bool IsDone
        {
            get
            {
                // Make sure we've not already completed this tag
                if (bAlreadyCompleted.HasValue && bAlreadyCompleted == true)
                {
                    return true;
                }
                if (!bComplexDoneCheck.HasValue)
                {
                    bComplexDoneCheck = new bool?(GetConditionExec());
                }
                if (bComplexDoneCheck == false)
                {
                    return true;
                }
                if (funcBehaviorProcess == null)
                {
                    funcBehaviorProcess = new Func<ProfileBehavior, bool>(CheckBehaviorIsDone);
                }
                bool bAllChildrenDone = Body.All<ProfileBehavior>(funcBehaviorProcess);
                if (bAllChildrenDone)
                {
                    bAlreadyCompleted = true;
                }
                return bAllChildrenDone;
            }
        }
    }

    // ************************************************************************************************
    // ***** TrinityUseReset - Resets a UseOnce tag as if it has never been used                  *****
    // ************************************************************************************************

    [XmlElement("TrinityUseReset")]
    public class TrinityUseResetTag : ProfileBehavior
    {
        private bool m_IsDone = false;
        private int iID;
        public override bool IsDone
        {
            get { return m_IsDone; }
        }

        protected override Composite CreateBehavior()
        {
            return new Zeta.TreeSharp.Action(ret =>
            {
                // See if we've EVER hit this ID before
                // If so, delete it, if not, do nothing
                if (GilesTrinity.hashUseOnceID.Contains(ID))
                {
                    GilesTrinity.hashUseOnceID.Remove(ID);
                    GilesTrinity.dictUseOnceID.Remove(ID);
                }
                m_IsDone = true;
            });
        }


        [XmlAttribute("id")]
        public int ID
        {
            get
            {
                return iID;
            }
            set
            {
                iID = value;
            }
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }
    }

    // ************************************************************************************************
    // ***** TrinityUseStop - prevents a useonce tag ID ever being used again                     *****
    // ************************************************************************************************

    [XmlElement("TrinityUseStop")]
    public class TrinityUseStopTag : ProfileBehavior
    {
        private bool m_IsDone = false;
        private int iID;
        public override bool IsDone
        {
            get { return m_IsDone; }
        }

        protected override Composite CreateBehavior()
        {
            return new Zeta.TreeSharp.Action(ret =>
            {
                // See if we've EVER hit this ID before
                // If so, set it disabled - if not, add it and prevent it
                if (GilesTrinity.hashUseOnceID.Contains(ID))
                {
                    GilesTrinity.dictUseOnceID[ID] = -1;
                }
                else
                {
                    GilesTrinity.hashUseOnceID.Add(ID);
                    GilesTrinity.dictUseOnceID.Add(ID, -1);
                }
                m_IsDone = true;
            });
        }


        [XmlAttribute("id")]
        public int ID
        {
            get
            {
                return iID;
            }
            set
            {
                iID = value;
            }
        }

        public override void ResetCachedDone()
        {
            m_IsDone = false;
            base.ResetCachedDone();
        }
    }

    // ************************************************************************************************
    // ***** TrinityUseOnce ensures a sequence of tags is only ever used once during this profile *****
    // ************************************************************************************************

    [XmlElement("TrinityUseOnce")]
    public class TrinityUseOnceTag : ComplexNodeTag
    {
        private bool? bComplexDoneCheck;
        private bool? bAlreadyCompleted;
        private Func<bool> funcConditionalProcess;
        private static Func<ProfileBehavior, bool> funcBehaviorProcess;
        private int iUniqueID;
        private int iMaxRedo;
        private string sDisablePrevious;

        protected override Composite CreateBehavior()
        {
            PrioritySelector decorated = new PrioritySelector(new Composite[0]);
            foreach (ProfileBehavior behavior in base.GetNodes())
            {
                decorated.AddChild(behavior.Behavior);
            }
            return new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckNotAlreadyDone), decorated);
        }

        public bool GetConditionExec()
        {
            // See if we've EVER hit this ID before
            if (GilesTrinity.hashUseOnceID.Contains(ID))
            {
                // See if we've hit it more than or equal to the max times before
                if (GilesTrinity.dictUseOnceID[ID] >= Max || GilesTrinity.dictUseOnceID[ID] < 0)
                    return false;
                // Add 1 to our hit count, and let it run this time
                GilesTrinity.dictUseOnceID[ID]++;
                return true;
            }
            // Never hit this before, so create the entry and let it run
            // First see if we should disable all other ID's currently hit to prevent them ever being run again this run
            if (DisablePrevious != null && DisablePrevious.ToLower() == "true")
            {
                foreach (int thisid in GilesTrinity.hashUseOnceID)
                {
                    if (thisid != ID)
                    {
                        GilesTrinity.dictUseOnceID[thisid] = -1;
                    }
                }
            }
            // Now store the fact we have hit this ID and set up the dictionary entry for it
            GilesTrinity.hashUseOnceID.Add(ID);
            GilesTrinity.dictUseOnceID.Add(ID, 1);
            return true;
        }

        private bool CheckNotAlreadyDone(object object_0)
        {
            return !IsDone;
        }

        public override void ResetCachedDone()
        {
            foreach (ProfileBehavior behavior in Body)
            {
                behavior.ResetCachedDone();
            }
            bComplexDoneCheck = null;
        }

        private static bool CheckBehaviorIsDone(ProfileBehavior profileBehavior)
        {
            return profileBehavior.IsDone;
        }

        [XmlAttribute("id")]
        public int ID
        {
            get
            {
                return iUniqueID;
            }
            set
            {
                iUniqueID = value;
            }
        }

        [XmlAttribute("disableprevious")]
        public string DisablePrevious
        {
            get
            {
                return sDisablePrevious;
            }
            set
            {
                sDisablePrevious = value;
            }
        }

        [XmlAttribute("max")]
        public int Max
        {
            get
            {
                return iMaxRedo;
            }
            set
            {
                iMaxRedo = value;
            }
        }

        public Func<bool> Conditional
        {
            get
            {
                return funcConditionalProcess;
            }
            set
            {
                funcConditionalProcess = value;
            }
        }

        public override bool IsDone
        {
            get
            {
                // Make sure we've not already completed this tag
                if (bAlreadyCompleted.HasValue && bAlreadyCompleted == true)
                {
                    return true;
                }
                if (!bComplexDoneCheck.HasValue)
                {
                    bComplexDoneCheck = new bool?(GetConditionExec());
                }
                if (bComplexDoneCheck == false)
                {
                    return true;
                }
                if (funcBehaviorProcess == null)
                {
                    funcBehaviorProcess = new Func<ProfileBehavior, bool>(CheckBehaviorIsDone);
                }
                bool bAllChildrenDone = Body.All<ProfileBehavior>(funcBehaviorProcess);
                if (bAllChildrenDone)
                {
                    bAlreadyCompleted = true;
                }
                return bAllChildrenDone;
            }
        }
    }


    // **********************************************************************************************
    // *****                Blank Stuck Handler - to disable DB stuck handler                   *****
    // **********************************************************************************************
    public class GilesStuckHandler : IStuckHandler
    {
        public Vector3 GetUnstuckPos()
        {
            return Vector3.Zero;
        }

        public bool IsStuck
        {
            get
            {
                return false;
            }
        }
    }



    // **********************************************************************************************
    // *****                             Player Mover Class                                     *****
    // **********************************************************************************************
    public class GilesPlayerMover : IPlayerMover
    {

        private static readonly HashSet<int> hashAvoidLeapingToSNO = new HashSet<int> { 138989, 176074, 176076, 176077, 176536, 260330 };
        // 138989 = health pool, 176074 = protection, 176076 = fortune, 176077 = frenzied, 176536 = portal in leorics, 260330 = cooldown shrine
        // Exp shrines = ???? Other shrines ????
        private static bool ShrinesInArea(Vector3 targetpos)
        {
            return ZetaDia.Actors.GetActorsOfType<DiaObject>(true).Any(u => hashAvoidLeapingToSNO.Contains(u.ActorSNO) && Vector3.Distance(u.Position, targetpos) <= 10f);
        } // ShrinesInArea checker

        public void MoveStop()
        {
            ZetaDia.Me.UsePower(SNOPower.Walk, ZetaDia.Me.Position, GilesTrinity.iCurrentWorldID, -1);
        }
        // Anti-stuck variables
        private static Vector3 vOldMoveToTarget = Vector3.Zero;
        public static int iTimesReachedStuckPoint = 0;
        public static int iTotalAntiStuckAttempts = 1;
        public static Vector3 vSafeMovementLocation = Vector3.Zero;
        public static DateTime timeLastRecordedPosition = DateTime.Today;
        public static Vector3 vOldPosition = Vector3.Zero;
        public static DateTime timeStartedUnstuckMeasure = DateTime.Today;
        public static int iTimesReachedMaxUnstucks = 0;
        public static DateTime timeCancelledUnstuckerFor = DateTime.Today;
        public static DateTime timeLastReportedAnyStuck = DateTime.Today;
        public static int iCancelUnstuckerForSeconds = 60;
        public static DateTime timeLastRestartedGame = DateTime.Today;

        // **********************************************************************************************
        // *****                         Check if we are stuck or not                               *****
        // **********************************************************************************************
        // Simply checks for position changing max once every 3 seconds, to decide on stuck
        public static bool UnstuckChecker(Vector3 vMyCurrentPosition)
        {
            // Keep checking distance changes every 3 seconds
            if (DateTime.Now.Subtract(timeLastRecordedPosition).TotalMilliseconds >= 3000)
            {
                timeLastRecordedPosition = DateTime.Now;
                if (vOldPosition != Vector3.Zero && vOldPosition.Distance(vMyCurrentPosition) <= 4f)
                {
                    return true;
                }
                vOldPosition = vMyCurrentPosition;
            }
            return false;
        }

        // **********************************************************************************************
        // *****                Actually deal with a stuck - find an unstuck point etc.             *****
        // **********************************************************************************************
        public static Vector3 UnstuckHandler(Vector3 vMyCurrentPosition, Vector3 vOriginalDestination)
        {
            // Update the last time we generated a path
            timeStartedUnstuckMeasure = DateTime.Now;



            // If we got stuck on a 2nd/3rd/4th "chained" anti-stuck route, then return the old move to target to keep movement of some kind going
            if (iTimesReachedStuckPoint > 0)
            {
                vSafeMovementLocation = Vector3.Zero;
                iTimesReachedStuckPoint++;
                // Reset the path and allow a whole "New" unstuck generation next cycle
                iTimesReachedStuckPoint = 0;
                // And cancel unstucking for 9 seconds so DB can try to navigate
                iCancelUnstuckerForSeconds = (9 * iTotalAntiStuckAttempts);
                if (iCancelUnstuckerForSeconds < 20)
                    iCancelUnstuckerForSeconds = 20;
                timeCancelledUnstuckerFor = DateTime.Now;
                Navigator.Clear();
                Logging.WriteDiagnostic("[GilesTrinity] Clearing old route and trying new path find to: " + vOldMoveToTarget.ToString());
                Navigator.MoveTo(vOldMoveToTarget, "original destination", false);
                return vSafeMovementLocation;
            }

            // Only try an unstuck 10 times maximum in XXX period of time
            if (Vector3.Distance(vOriginalDestination, vMyCurrentPosition) >= 700f)
            {
                Logging.Write("[GilesTrinity] You are " + Vector3.Distance(vOriginalDestination, vMyCurrentPosition).ToString() + " distance away from your destination.");
                Logging.Write("[GilesTrinity] This is too far for the unstucker, and is likely a sign of ending up in the wrong map zone.");
                iTotalAntiStuckAttempts = 20;
            }
            //intell
            if (iTotalAntiStuckAttempts <= 15)
            {
                Logging.Write("[GilesTrinity] Your bot got stuck! Trying to unstuck (attempt #" + iTotalAntiStuckAttempts.ToString() + " of 15 attempts)");
                Logging.WriteDiagnostic("(destination=" + vOriginalDestination.ToString() + ", which is " + Vector3.Distance(vOriginalDestination, vMyCurrentPosition).ToString() + " distance away)");

                GilesTrinity.playerStatus.vCurrentPosition = vMyCurrentPosition;
                vSafeMovementLocation = GilesTrinity.FindSafeZone(true, iTotalAntiStuckAttempts, Vector3.Zero);

                // Temporarily log stuff
                if (iTotalAntiStuckAttempts == 1 && GilesTrinity.settings.bLogStucks)
                {
                    FileStream LogStream = File.Open(GilesTrinity.sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - Stucks - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read);
                    using (StreamWriter LogWriter = new StreamWriter(LogStream))
                    {
                        LogWriter.WriteLine(DateTime.Now.ToString() + ": Original Destination=" + vOldMoveToTarget.ToString() + ". Current player position when stuck=" + vMyCurrentPosition.ToString());
                        LogWriter.WriteLine("Profile Name=" + ProfileManager.CurrentProfile.Name);
                    }
                    LogStream.Close();
                }

                // Now count up our stuck attempt generations
                iTotalAntiStuckAttempts++;
                return vSafeMovementLocation;
            }
            iTimesReachedMaxUnstucks++;
            iTotalAntiStuckAttempts = 1;
            vSafeMovementLocation = Vector3.Zero;
            vOldPosition = Vector3.Zero;
            iTimesReachedStuckPoint = 0;
            timeLastRecordedPosition = DateTime.Today;
            timeStartedUnstuckMeasure = DateTime.Today;
            int iSafetyLoops = 0;
            if (iTimesReachedMaxUnstucks == 1)
            {
                Navigator.Clear();
                Logging.Write("[GilesTrinity] Anti-stuck measures now attempting to kickstart DB's path-finder into action.");
                Navigator.MoveTo(vOriginalDestination, "original destination", false);
                iCancelUnstuckerForSeconds = 40;
                timeCancelledUnstuckerFor = DateTime.Now;
                return vSafeMovementLocation;
            }
            if (iTimesReachedMaxUnstucks == 2)
            {
                Logging.Write("[GilesTrinity] Anti-stuck measures failed. Now attempting to reload current profile.");
                // First see if we need to, and can, teleport to town
                while (!ZetaDia.Me.IsInTown)
                {
                    iSafetyLoops++;
                    GilesTrinity.WaitWhileAnimating(5, true);
                    ZetaDia.Me.UsePower(SNOPower.UseStoneOfRecall, ZetaDia.Me.Position, ZetaDia.Me.WorldDynamicId, -1);
                    Thread.Sleep(1000);
                    GilesTrinity.WaitWhileAnimating(1000, true);
                    if (iSafetyLoops > 5)
                        break;
                }
                Thread.Sleep(1000);
                // As long as we successfully reached town, reload the profile
                if (ZetaDia.Me.IsInTown)
                {
                    ProfileManager.Load(Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile);
                    Logging.Write("[GilesTrinity] Anti-stuck successfully reloaded current profile, DemonBuddy now navigating again.");
                    Thread.Sleep(3000);
                    return vSafeMovementLocation;
                }
                // Didn't make it to town, so skip instantly to the exit game system
                iTimesReachedMaxUnstucks = 3;
            }
            // Exit the game and reload the profile
            if (GilesTrinity.settings.bEnableProfileReloading && DateTime.Now.Subtract(timeLastRestartedGame).TotalMinutes >= 15)
            {
                timeLastRestartedGame = DateTime.Now;
                string sUseProfile = GilesTrinity.sFirstProfileSeen;
                Logging.Write("[GilesTrinity] Anti-stuck measures exiting current game.");
                // Load the first profile seen last run
                ProfileManager.Load(!string.IsNullOrEmpty(sUseProfile)
                                        ? sUseProfile
                                        : Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile);
                Thread.Sleep(1000);
                GilesTrinity.GilesResetEverythingNewGame();
                ZetaDia.Service.Games.LeaveGame();
                // Wait for 10 second log out timer if not in town
                if (!ZetaDia.Me.IsInTown)
                {
                    Thread.Sleep(10000);
                }
            }
            else
            {
                Logging.Write("[GilesTrinity] Unstucking measures failed. Now stopping Trinity unstucker for 12 minutes to inactivity timers to kick in or DB to auto-fix.");
                iCancelUnstuckerForSeconds = 720;
                timeCancelledUnstuckerFor = DateTime.Now;
                return vSafeMovementLocation;
            }
            return vSafeMovementLocation;
        }

        // **********************************************************************************************
        // *****               Handle moveto requests from the current routine/profile              *****
        // **********************************************************************************************
        // This replaces DemonBuddy's own built-in "Basic movement handler" with a custom one
        private static Vector3 vLastMoveTo = Vector3.Zero;
        private static bool bLastWaypointWasTown = false;
        private static HashSet<Vector3> hashDoneThisVector = new HashSet<Vector3>();

        private static Vector3 vShiftedPosition = Vector3.Zero;
        private static DateTime lastShiftedPosition = DateTime.Today;
        private static int iShiftPositionFor = 0;

        public void MoveTowards(Vector3 vMoveToTarget)
        {
            // Recording of all the XML's in use this run
            string sThisProfile = Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile;
            if (sThisProfile != GilesTrinity.sLastProfileSeen)
            {
                // See if we appear to have started a new game
                if (GilesTrinity.sFirstProfileSeen != "" && sThisProfile == GilesTrinity.sFirstProfileSeen)
                {
                    GilesTrinity.iTotalProfileRecycles++;
                    if (GilesTrinity.iTotalProfileRecycles > GilesTrinity.iTotalJoinGames && GilesTrinity.iTotalProfileRecycles > GilesTrinity.iTotalLeaveGames)
                    {
                        GilesTrinity.GilesResetEverythingNewGame();
                    }
                }
                GilesTrinity.listProfilesLoaded.Add(sThisProfile);
                GilesTrinity.sLastProfileSeen = sThisProfile;
                if (GilesTrinity.sFirstProfileSeen == "")
                    GilesTrinity.sFirstProfileSeen = sThisProfile;
            }

            // The below code is to help profile/routine makers avoid waypoints with a long distance between them.
            // Long-distances between waypoints is bad - it increases stucks, and forces the DB nav-server to be called.
            if (GilesTrinity.settings.bLogStucks)
            {
                if (vLastMoveTo == Vector3.Zero)
                    vLastMoveTo = vMoveToTarget;

                if (vMoveToTarget != vLastMoveTo)
                {
                    float fDistance = Vector3.Distance(vMoveToTarget, vLastMoveTo);
                    // Log if not in town, last waypoint wasn't FROM town, and the distance is >200 but <2000 (cos 2000+ probably means we changed map zones!)
                    if (!ZetaDia.Me.IsInTown && !bLastWaypointWasTown && fDistance >= 200 & fDistance <= 2000)
                    {
                        if (!hashDoneThisVector.Contains(vMoveToTarget))
                        {
                            // Log it
                            FileStream LogStream = File.Open(GilesTrinity.sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - LongPaths - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read);
                            using (StreamWriter LogWriter = new StreamWriter(LogStream))
                            {
                                LogWriter.WriteLine(DateTime.Now.ToString() + ":");
                                LogWriter.WriteLine("Profile Name=" + ProfileManager.CurrentProfile.Name);
                                LogWriter.WriteLine("'From' Waypoint=" + vLastMoveTo.ToString() + ". 'To' Waypoint=" + vMoveToTarget.ToString() + ". Distance=" + fDistance.ToString());
                            }
                            LogStream.Close();
                            hashDoneThisVector.Add(vMoveToTarget);
                        }
                    }
                    vLastMoveTo = vMoveToTarget;
                    bLastWaypointWasTown = false;
                    if (ZetaDia.Me.IsInTown)
                        bLastWaypointWasTown = true;
                }
            }

            // Make sure GilesTrinity doesn't want us to avoid routine-movement
            if (GilesTrinity.bDontMoveMeIAmDoingShit)
                return;

            // Store player current position
            Vector3 vMyCurrentPosition = ZetaDia.Me.Position;

            // Store distance to current moveto target
            float fDistanceFromTarget;

            // Do unstuckery things
            if (GilesTrinity.settings.bEnableUnstucker)
            {
                // Store the "real" (not anti-stuck) destination
                vOldMoveToTarget = vMoveToTarget;

                // See if we can reset the 10-limit unstuck counter, if >120 seconds since we last generated an unstuck location
                if (iTotalAntiStuckAttempts > 1 && DateTime.Now.Subtract(timeStartedUnstuckMeasure).TotalSeconds >= 120)
                {
                    iTotalAntiStuckAttempts = 1;
                    iTimesReachedStuckPoint = 0;
                    vSafeMovementLocation = Vector3.Zero;
                }

                // See if we need to, and can, generate unstuck actions
                if (DateTime.Now.Subtract(timeCancelledUnstuckerFor).TotalSeconds > iCancelUnstuckerForSeconds && UnstuckChecker(vMyCurrentPosition))
                {
                    // Record the time we last apparently couldn't move for a brief period of time
                    timeLastReportedAnyStuck = DateTime.Now;
                    // See if there's any stuck position to try and navigate to generated by random mover
                    vSafeMovementLocation = UnstuckHandler(vMyCurrentPosition, vOldMoveToTarget);
                    if (vSafeMovementLocation == Vector3.Zero)
                        return;
                }

                // See if we can clear the total unstuckattempts if we haven't been stuck in over 6 minutes.
                if (DateTime.Now.Subtract(timeLastReportedAnyStuck).TotalSeconds >= 360)
                {
                    iTimesReachedMaxUnstucks = 0;
                }

                // Did we have a safe point already generated (eg from last loop through), if so use it as our current location instead
                if (vSafeMovementLocation != Vector3.Zero)
                {
                    // Set our current movement target to the safe point we generated last cycle
                    vMoveToTarget = vSafeMovementLocation;
                }

                // Get distance to current destination
                fDistanceFromTarget = Vector3.Distance(vMyCurrentPosition, vMoveToTarget);

                // Remove the stuck position if it's been reached, this bit of code also creates multiple stuck-patterns in an ever increasing amount
                if (vSafeMovementLocation != Vector3.Zero && fDistanceFromTarget <= 3f)
                {
                    vSafeMovementLocation = Vector3.Zero;
                    iTimesReachedStuckPoint++;
                    // Do we want to immediately generate a 2nd waypoint to "chain" anti-stucks in an ever-increasing path-length?
                    if (iTimesReachedStuckPoint <= iTotalAntiStuckAttempts)
                    {
                        GilesTrinity.playerStatus.vCurrentPosition = vMyCurrentPosition;
                        vSafeMovementLocation = GilesTrinity.FindSafeZone(true, iTotalAntiStuckAttempts, Vector3.Zero);
                        vMoveToTarget = vSafeMovementLocation;
                    }
                    else
                    {
                        Logging.WriteDiagnostic("[GilesTrinity] Clearing old route and trying new path find to: " + vOldMoveToTarget.ToString());
                        // Reset the path and allow a whole "New" unstuck generation next cycle
                        iTimesReachedStuckPoint = 0;
                        // And cancel unstucking for 9 seconds so DB can try to navigate
                        iCancelUnstuckerForSeconds = (9 * iTotalAntiStuckAttempts);
                        if (iCancelUnstuckerForSeconds < 20)
                            iCancelUnstuckerForSeconds = 20;
                        timeCancelledUnstuckerFor = DateTime.Now;
                        Navigator.Clear();
                        Navigator.MoveTo(vOldMoveToTarget, "original destination", false);
                        return;
                    }
                }
            }
            else
            {
                // Get distance to current destination
                fDistanceFromTarget = Vector3.Distance(vMyCurrentPosition, vMoveToTarget);
            } // Is the built-in unstucker enabled or not?

            // See if there's an obstacle in our way, if so try to navigate around it
            Vector3 point = vMoveToTarget;
            foreach (GilesTrinity.GilesObstacle tempobstacle in GilesTrinity.hashNavigationObstacleCache.Where(cp =>
                            GilesTrinity.GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, vMyCurrentPosition, point) &&
                            cp.vThisLocation.Distance(vMyCurrentPosition) > GilesTrinity.dictSNONavigationSize[cp.iThisSNOID]))
            {
                if (vShiftedPosition == Vector3.Zero)
                {
                    if (DateTime.Now.Subtract(lastShiftedPosition).TotalSeconds >= 10)
                    {
                        float fDirectionToTarget = GilesTrinity.FindDirectionDegree(vMyCurrentPosition, vMoveToTarget);
                        vMoveToTarget = MathEx.GetPointAt(vMyCurrentPosition, 15f, MathEx.ToRadians(fDirectionToTarget - 50));
                        if (!GilesTrinity.GilesCanRayCast(vMyCurrentPosition, vMoveToTarget, NavCellFlags.AllowWalk))
                        {
                            vMoveToTarget = MathEx.GetPointAt(vMyCurrentPosition, 15f, MathEx.ToRadians(fDirectionToTarget + 50));
                            if (!GilesTrinity.GilesCanRayCast(vMyCurrentPosition, vMoveToTarget, NavCellFlags.AllowWalk))
                            {
                                vMoveToTarget = point;
                            }
                        }
                        if (vMoveToTarget != point)
                        {
                            vShiftedPosition = vMoveToTarget;
                            iShiftPositionFor = 900;
                            lastShiftedPosition = DateTime.Now;
                            Logging.WriteDiagnostic("[GilesTrinity] Navigation handler position shift to: " + vMoveToTarget.ToString() + " (was " + point.ToString() + ")");
                        }
                    } // Make sure we only shift max once every 10 seconds
                }
                else
                {
                    if (DateTime.Now.Subtract(lastShiftedPosition).TotalMilliseconds <= iShiftPositionFor)
                    {
                        vMoveToTarget = vShiftedPosition;
                    }
                    else
                    {
                        vShiftedPosition = Vector3.Zero;
                    }
                }
            }

            // See if we can use abilities like leap etc. for movement out of combat, but not in town
            if (GilesTrinity.settings.bOutOfCombatMovementPowers && !ZetaDia.Me.IsInTown)
            {
                bool bTooMuchZChange = ((vMyCurrentPosition.Z - vMoveToTarget.Z) >= 4f);
                // Leap movement for a barb
                if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Leap) &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_Leap]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Barbarian_Leap] &&
                    fDistanceFromTarget >= 20f &&
                    PowerManager.CanCast(SNOPower.Barbarian_Leap) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (fDistanceFromTarget > 35f)
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_Leap, vThisTarget, GilesTrinity.iCurrentWorldID, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_Leap] = DateTime.Now;
                    return;
                }
                // Furious Charge movement for a barb
                if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_FuriousCharge) && !bTooMuchZChange &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Barbarian_FuriousCharge] &&
                    fDistanceFromTarget >= 20f &&
                    PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (fDistanceFromTarget > 35f)
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_FuriousCharge, vThisTarget, GilesTrinity.iCurrentWorldID, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge] = DateTime.Now;
                    return;
                }
                // Vault for a DH - maximum set by user-defined setting
                if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Vault) && !bTooMuchZChange &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault]).TotalMilliseconds >= GilesTrinity.settings.iDHVaultMovementDelay &&
                    fDistanceFromTarget >= 18f &&
                    PowerManager.CanCast(SNOPower.DemonHunter_Vault) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (fDistanceFromTarget > 35f)
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    ZetaDia.Me.UsePower(SNOPower.DemonHunter_Vault, vThisTarget, GilesTrinity.iCurrentWorldID, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault] = DateTime.Now;
                    return;
                }
                // Tempest rush for a monk
                if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Monk_TempestRush) && !bTooMuchZChange && ZetaDia.Me.CurrentPrimaryResource >= 20)
                {
                    Vector3 vTargetAimPoint = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 10f);
                    ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, vTargetAimPoint, GilesTrinity.iCurrentWorldID, -1);
                    return;
                }
                // Teleport for a wizard (need to be able to check skill rune in DB for a 3-4 teleport spam in a row)
                if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Teleport) &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Wizard_Teleport] &&
                    fDistanceFromTarget >= 20f &&
                    PowerManager.CanCast(SNOPower.Wizard_Teleport) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (fDistanceFromTarget > 35f)
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    ZetaDia.Me.UsePower(SNOPower.Wizard_Teleport, vThisTarget, GilesTrinity.iCurrentWorldID, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport] = DateTime.Now;
                    return;
                }
                // Archon Teleport for a wizard 
                if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon_Teleport) &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Wizard_Archon_Teleport] &&
                    fDistanceFromTarget >= 20f &&
                    PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (fDistanceFromTarget > 35f)
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    ZetaDia.Me.UsePower(SNOPower.Wizard_Archon_Teleport, vThisTarget, GilesTrinity.iCurrentWorldID, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport] = DateTime.Now;
                    return;
                }
            } // Allowed to use movement powers to move out-of-combat?

            ZetaDia.Me.UsePower(SNOPower.Walk, vMoveToTarget, GilesTrinity.iCurrentWorldID, -1);
        }
    }


    // **********************************************************************************************
    // *****                    Give DB a Blank Combat Target Provider                          *****
    // **********************************************************************************************

    public class GilesCombatTargetingReplacer : ITargetingProvider
    {
        private static readonly List<DiaObject> listEmptyList = new List<DiaObject>();
        public List<DiaObject> GetObjectsByWeight()
        {
            if (!GilesTrinity.bDontMoveMeIAmDoingShit || GilesTrinity.thisFakeObject == null)
                return listEmptyList;
            List<DiaObject> listFakeList = new List<DiaObject>();
            listFakeList.Add(GilesTrinity.thisFakeObject);
            return listFakeList;
        }
    }
    public class GilesLootTargetingProvider : ITargetingProvider
    {
        private static readonly List<DiaObject> listEmptyList = new List<DiaObject>();
        public List<DiaObject> GetObjectsByWeight()
        {
            return listEmptyList;
        }
    }
    public class GilesObstacleTargetingProvider : ITargetingProvider
    {
        private static readonly List<DiaObject> listEmptyList = new List<DiaObject>();
        public List<DiaObject> GetObjectsByWeight()
        {
            return listEmptyList;
        }
    }

}
